From a2dcaa097af0c6bb70b49e3f2d436ee0aaedf867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Le=C5=9Bniak?= Date: Mon, 30 Sep 2024 16:15:48 +0200 Subject: [PATCH 1/2] done --- bin/runtime/src/lib.rs | 152 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/bin/runtime/src/lib.rs b/bin/runtime/src/lib.rs index e341711ae9..22dd221344 100644 --- a/bin/runtime/src/lib.rs +++ b/bin/runtime/src/lib.rs @@ -1362,6 +1362,7 @@ impl_runtime_apis! { #[cfg(test)] mod tests { use frame_support::traits::Get; + use pallet_staking::EraPayout; use primitives::HEAP_PAGES; use smallvec::Array; @@ -1589,4 +1590,155 @@ mod tests { assert!(lhs < rhs); } + + #[test] + fn era_payout() { + use sp_io::TestExternalities; + + struct Inputs { + azero_cap: Balance, + horizon: u64, + // will be ignored + total_staked: Balance, + total_issuance: Balance, + era_duration_millis: u64, + } + + struct Outputs { + validators_payout: Balance, + rest: Balance, + } + + fn assert_on_data(inputs: Inputs, outputs: Outputs) { + TestExternalities::default().execute_with(|| { + pallet_aleph::AzeroCap::::put(inputs.azero_cap); + pallet_aleph::ExponentialInflationHorizon::::put(inputs.horizon); + let (validators_payout, rest) = + ::EraPayout::era_payout( + inputs.total_staked, + inputs.total_issuance, + inputs.era_duration_millis, + ); + assert_eq!(validators_payout, outputs.validators_payout); + assert_eq!(rest, outputs.rest); + }); + } + + fn run_for_n_eras(inputs: Inputs, n_eras: usize) -> (Vec, Balance) { + let mut outputs = vec![]; + let mut total_issuance = inputs.total_issuance; + for _ in 0..n_eras { + TestExternalities::default().execute_with(|| { + pallet_aleph::AzeroCap::::put(inputs.azero_cap); + pallet_aleph::ExponentialInflationHorizon::::put(inputs.horizon); + let (validators_payout, rest) = + ::EraPayout::era_payout( + inputs.total_staked, + total_issuance, + inputs.era_duration_millis, + ); + outputs.push(Outputs { + validators_payout, + rest, + }); + total_issuance += validators_payout + rest; + }); + } + (outputs, total_issuance) + } + + const MILLISECS_PER_DAY: u64 = 24 * 60 * 60 * 1000; + + // standard case + assert_on_data( + Inputs { + azero_cap: 100_000_000 * TOKEN, + horizon: 365 * MILLISECS_PER_DAY, + total_staked: 0, + total_issuance: 50_000_000 * TOKEN, + era_duration_millis: MILLISECS_PER_DAY, + }, + Outputs { + validators_payout: 123_118 * TOKEN + 920_000_000_000, + rest: 13_679 * TOKEN + 880_000_000_000, + }, + ); + + // After 3 * horizon milliseconds the gap should be reduced by ~95% + let (_, total_issuance) = run_for_n_eras( + Inputs { + azero_cap: 150_000_000 * TOKEN, + horizon: 365 * MILLISECS_PER_DAY, + total_staked: 0, + total_issuance: 50_000_000 * TOKEN, + era_duration_millis: MILLISECS_PER_DAY, + }, + 3 * 365, + ); + assert_eq!(total_issuance, 145_021_290 * TOKEN + 959_387_724_274); + + // era longer than horizon + // Perbill will saturate, (era_duration_millis / horizon) == 1, + // even if horizon == 0 + // the actual values do not matter, only the ratio is used + // we expect the gap to be reduced by ~63% + assert_on_data( + Inputs { + azero_cap: 100_000_000 * TOKEN, + horizon: 0, + total_staked: 0, + total_issuance: 50_000_000 * TOKEN, + era_duration_millis: MILLISECS_PER_DAY, + }, + Outputs { + validators_payout: 28_499_999 * TOKEN + 985_000_000_000, + rest: 3_166_666 * TOKEN + 665_000_000_000, + }, + ); + + // cap equal to issuance + assert_on_data( + Inputs { + azero_cap: 100_000_000 * TOKEN, + horizon: 365 * MILLISECS_PER_DAY, + total_staked: 0, + total_issuance: 100_000_000 * TOKEN, + era_duration_millis: MILLISECS_PER_DAY, + }, + Outputs { + validators_payout: 0, + rest: 0, + }, + ); + + // cap smaller than issuance + assert_on_data( + Inputs { + azero_cap: 50_000_000 * TOKEN, + horizon: 365 * MILLISECS_PER_DAY, + total_staked: 0, + total_issuance: 100_000_000 * TOKEN, + era_duration_millis: MILLISECS_PER_DAY, + }, + Outputs { + validators_payout: 0, + rest: 0, + }, + ); + + // zero-lenght era + assert_on_data( + Inputs { + azero_cap: 100_000_000 * TOKEN, + horizon: 365 * MILLISECS_PER_DAY, + total_staked: 0, + total_issuance: 50_000_000 * TOKEN, + era_duration_millis: 0, + }, + Outputs { + validators_payout: 0, + rest: 0, + }, + ); + } } From edd6fab9d7cf266119cc51a52a38f0c1cb36b714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Le=C5=9Bniak?= Date: Fri, 4 Oct 2024 12:10:11 +0200 Subject: [PATCH 2/2] refactor --- bin/runtime/src/lib.rs | 169 ++++++++++++++++++++++------------------- 1 file changed, 91 insertions(+), 78 deletions(-) diff --git a/bin/runtime/src/lib.rs b/bin/runtime/src/lib.rs index 22dd221344..893fa46dfc 100644 --- a/bin/runtime/src/lib.rs +++ b/bin/runtime/src/lib.rs @@ -1591,151 +1591,164 @@ mod tests { assert!(lhs < rhs); } - #[test] - fn era_payout() { - use sp_io::TestExternalities; + /// `EraPayout::era_payout` ignores the first argument, we set it to zero. + const DUMMY_TOTAL_STAKED: Balance = 0; + const MILLISECS_PER_DAY: u64 = 24 * 60 * 60 * 1000; - struct Inputs { - azero_cap: Balance, - horizon: u64, - // will be ignored - total_staked: Balance, - total_issuance: Balance, - era_duration_millis: u64, - } + struct EraPayoutInputs { + azero_cap: Balance, + horizon: u64, + total_issuance: Balance, + era_duration_millis: u64, + } - struct Outputs { - validators_payout: Balance, - rest: Balance, - } + struct EraPayoutOutputs { + validators_payout: Balance, + rest: Balance, + } - fn assert_on_data(inputs: Inputs, outputs: Outputs) { + fn assert_era_payout(inputs: EraPayoutInputs, outputs: EraPayoutOutputs) { + use sp_io::TestExternalities; + TestExternalities::default().execute_with(|| { + pallet_aleph::AzeroCap::::put(inputs.azero_cap); + pallet_aleph::ExponentialInflationHorizon::::put(inputs.horizon); + let (validators_payout, rest) = + ::EraPayout::era_payout( + DUMMY_TOTAL_STAKED, + inputs.total_issuance, + inputs.era_duration_millis, + ); + assert_eq!(validators_payout, outputs.validators_payout); + assert_eq!(rest, outputs.rest); + }); + } + + fn era_payout_multiple_eras( + inputs: EraPayoutInputs, + n_eras: usize, + ) -> (Vec, Balance) { + use sp_io::TestExternalities; + let mut outputs = vec![]; + let mut total_issuance = inputs.total_issuance; + for _ in 0..n_eras { TestExternalities::default().execute_with(|| { pallet_aleph::AzeroCap::::put(inputs.azero_cap); pallet_aleph::ExponentialInflationHorizon::::put(inputs.horizon); let (validators_payout, rest) = ::EraPayout::era_payout( - inputs.total_staked, - inputs.total_issuance, + DUMMY_TOTAL_STAKED, + total_issuance, inputs.era_duration_millis, ); - assert_eq!(validators_payout, outputs.validators_payout); - assert_eq!(rest, outputs.rest); - }); - } - - fn run_for_n_eras(inputs: Inputs, n_eras: usize) -> (Vec, Balance) { - let mut outputs = vec![]; - let mut total_issuance = inputs.total_issuance; - for _ in 0..n_eras { - TestExternalities::default().execute_with(|| { - pallet_aleph::AzeroCap::::put(inputs.azero_cap); - pallet_aleph::ExponentialInflationHorizon::::put(inputs.horizon); - let (validators_payout, rest) = - ::EraPayout::era_payout( - inputs.total_staked, - total_issuance, - inputs.era_duration_millis, - ); - outputs.push(Outputs { - validators_payout, - rest, - }); - total_issuance += validators_payout + rest; + outputs.push(EraPayoutOutputs { + validators_payout, + rest, }); - } - (outputs, total_issuance) + total_issuance += validators_payout + rest; + }); } + (outputs, total_issuance) + } - const MILLISECS_PER_DAY: u64 = 24 * 60 * 60 * 1000; - - // standard case - assert_on_data( - Inputs { + #[test] + fn era_payout_standard_case() { + assert_era_payout( + EraPayoutInputs { azero_cap: 100_000_000 * TOKEN, horizon: 365 * MILLISECS_PER_DAY, - total_staked: 0, total_issuance: 50_000_000 * TOKEN, era_duration_millis: MILLISECS_PER_DAY, }, - Outputs { + EraPayoutOutputs { validators_payout: 123_118 * TOKEN + 920_000_000_000, rest: 13_679 * TOKEN + 880_000_000_000, }, ); + } - // After 3 * horizon milliseconds the gap should be reduced by ~95% - let (_, total_issuance) = run_for_n_eras( - Inputs { + #[test] + /// Simulate long run by calling `era_payout` multiple times, + /// and keeping track of `total_issuance` between calls. + /// After 3 * horizon milliseconds the gap should be reduced by ~95%. + fn era_payout_long_run() { + let (_, total_issuance) = era_payout_multiple_eras( + EraPayoutInputs { azero_cap: 150_000_000 * TOKEN, horizon: 365 * MILLISECS_PER_DAY, - total_staked: 0, total_issuance: 50_000_000 * TOKEN, era_duration_millis: MILLISECS_PER_DAY, }, 3 * 365, ); assert_eq!(total_issuance, 145_021_290 * TOKEN + 959_387_724_274); + } - // era longer than horizon - // Perbill will saturate, (era_duration_millis / horizon) == 1, - // even if horizon == 0 - // the actual values do not matter, only the ratio is used - // we expect the gap to be reduced by ~63% - assert_on_data( - Inputs { + #[test] + /// Era longer than horizon. + /// Perbill will saturate, (era_duration_millis / horizon) == 1, + /// even if horizon == 0. + /// The actual values do not matter, only the ratio is used. + /// We expect the gap to be reduced by ~63%. + fn era_payout_horizon_too_short() { + assert_era_payout( + EraPayoutInputs { azero_cap: 100_000_000 * TOKEN, horizon: 0, - total_staked: 0, total_issuance: 50_000_000 * TOKEN, era_duration_millis: MILLISECS_PER_DAY, }, - Outputs { + EraPayoutOutputs { validators_payout: 28_499_999 * TOKEN + 985_000_000_000, rest: 3_166_666 * TOKEN + 665_000_000_000, }, ); + } - // cap equal to issuance - assert_on_data( - Inputs { + #[test] + /// AZERO cap equal to total issuance, we expect no payout. + fn era_payout_cap_reached() { + assert_era_payout( + EraPayoutInputs { azero_cap: 100_000_000 * TOKEN, horizon: 365 * MILLISECS_PER_DAY, - total_staked: 0, total_issuance: 100_000_000 * TOKEN, era_duration_millis: MILLISECS_PER_DAY, }, - Outputs { + EraPayoutOutputs { validators_payout: 0, rest: 0, }, ); + } - // cap smaller than issuance - assert_on_data( - Inputs { + #[test] + /// Total issuance larger than AZERO cap, we expect no payout. + fn era_payout_cap_exceeded() { + assert_era_payout( + EraPayoutInputs { azero_cap: 50_000_000 * TOKEN, horizon: 365 * MILLISECS_PER_DAY, - total_staked: 0, total_issuance: 100_000_000 * TOKEN, era_duration_millis: MILLISECS_PER_DAY, }, - Outputs { + EraPayoutOutputs { validators_payout: 0, rest: 0, }, ); + } - // zero-lenght era - assert_on_data( - Inputs { + #[test] + /// Zero-length era, we expect no payout (as it depends on era lenght). + fn era_payout_zero_lenght_era() { + assert_era_payout( + EraPayoutInputs { azero_cap: 100_000_000 * TOKEN, horizon: 365 * MILLISECS_PER_DAY, - total_staked: 0, total_issuance: 50_000_000 * TOKEN, era_duration_millis: 0, }, - Outputs { + EraPayoutOutputs { validators_payout: 0, rest: 0, },