From 628bceb80b61d86d3e7730cc84adff784783910b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 10 May 2024 10:54:15 +0100 Subject: [PATCH] `stake-tracker` feedback PR (#4411) - Changes the target list score type from balance to `u128` (`ExtendedBalance`) - simplifies `balance/ vote_weight/ extended_balance` conversions - relies less on the currency total issuance call - ensures approval stake calculations/storage is safe - Simplifies stake tracker logic - removes a few redundant checks - split `on_stake_update` code - Ensures `VoterUpdateMode` is taken into consideration in try-state checks - use voter mode lazy in benchmarks - update weights + check the diff with voter lazy - Docs improvements - All tests passing after merging validator disabling and virtual stakers **new** - ensures duplicate nominations are dedup before calculating the approvals stake - chills nominator after total slash to ensure the target can be reaped/kill without leaving nominations behind. - ensures that switching from validator to nominator and back is correct - a nominator may have an entry in the targetlist (if it was a validator + has nominations) - ensure target node is removed if balance is 0 and it is a nominator. - addresses https://github.com/paritytech-secops/srlabs_findings/issues/383 --- **Other experiments:** (not included) - https://github.com/paritytech/polkadot-sdk/pull/4402 (passing the status of the staker with who in the OnStakingUpdate interface is less clean, it turns out) - https://github.com/paritytech/polkadot-sdk/pull/4361 --------- Co-authored-by: command-bot <> --- .../runtime/westend/src/bag_thresholds.rs | 2 +- polkadot/runtime/westend/src/lib.rs | 12 +- .../westend/src/weights/pallet_staking.rs | 9 + substrate/bin/node/runtime/src/lib.rs | 6 +- substrate/frame/staking/src/benchmarking.rs | 2 +- .../src/migrations/v13_stake_tracker/tests.rs | 4 +- substrate/frame/staking/src/mock.rs | 17 +- substrate/frame/staking/src/pallet/impls.rs | 4 +- substrate/frame/staking/src/pallet/mod.rs | 2 - substrate/frame/staking/src/tests.rs | 402 ++++++++++---- substrate/frame/staking/src/weights.rs | 522 +++++++++--------- .../frame/staking/stake-tracker/src/lib.rs | 494 ++++++++--------- .../frame/staking/stake-tracker/src/mock.rs | 41 +- .../frame/staking/stake-tracker/src/tests.rs | 115 ++-- substrate/primitives/staking/src/lib.rs | 6 + 15 files changed, 934 insertions(+), 704 deletions(-) diff --git a/polkadot/runtime/westend/src/bag_thresholds.rs b/polkadot/runtime/westend/src/bag_thresholds.rs index 9bb80ca3571d..ddbcc1b6aae4 100644 --- a/polkadot/runtime/westend/src/bag_thresholds.rs +++ b/polkadot/runtime/westend/src/bag_thresholds.rs @@ -234,7 +234,7 @@ pub const VOTER_THRESHOLDS: [u64; 200] = [ ]; /// Upper thresholds delimiting the targets bag list. -pub const TARGET_THRESHOLDS: [crate::Balance; 200] = [ +pub const TARGET_THRESHOLDS: [u128; 200] = [ 10_000_000_000, 11_131_723_507, 12_391_526_824, diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index f52dc0e37912..32b8b0c33c7c 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -578,7 +578,9 @@ impl pallet_election_provider_multi_phase::Config for Runtime { parameter_types! { pub const VoterBagThresholds: &'static [u64] = &bag_thresholds::VOTER_THRESHOLDS; - pub const TargetBagThresholds: &'static [Balance] = &bag_thresholds::TARGET_THRESHOLDS; + pub const TargetBagThresholds: &'static [u128] = &bag_thresholds::TARGET_THRESHOLDS; + + pub const VoterUpdateMode: pallet_stake_tracker::VoterUpdateMode = pallet_stake_tracker::VoterUpdateMode::Strict; } type VoterBagsListInstance = pallet_bags_list::Instance1; @@ -596,16 +598,15 @@ impl pallet_bags_list::Config for Runtime { type ScoreProvider = pallet_bags_list::Pallet; type WeightInfo = weights::pallet_bags_list::WeightInfo; type BagThresholds = TargetBagThresholds; - type Score = Balance; + type Score = u128; } impl pallet_stake_tracker::Config for Runtime { type Currency = Balances; - type RuntimeEvent = RuntimeEvent; type Staking = Staking; type VoterList = VoterList; type TargetList = TargetList; - type WeightInfo = (); // TODO + type VoterUpdateMode = VoterUpdateMode; } pallet_staking_reward_curve::build! { @@ -1680,7 +1681,8 @@ pub mod migrations { } /// Unreleased migrations. Add new ones here: - pub type Unreleased = (pallet_staking::migrations::v15::MigrateV14ToV15,); + pub type Unreleased = + (pallet_staking::migrations::single_block::v15::MigrateV14ToV15,); } /// Unchecked extrinsic type as expected by this runtime. diff --git a/polkadot/runtime/westend/src/weights/pallet_staking.rs b/polkadot/runtime/westend/src/weights/pallet_staking.rs index 393fa0b37176..0db16d5d47db 100644 --- a/polkadot/runtime/westend/src/weights/pallet_staking.rs +++ b/polkadot/runtime/westend/src/weights/pallet_staking.rs @@ -826,4 +826,13 @@ impl pallet_staking::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } + + // TODO: run CI bot benchmarks + fn drop_dangling_nomination() -> Weight { + Weight::default() + } + + fn v13_mmb_step() -> Weight { + Weight::default() + } } diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index ce33af29189d..f5385403c0e3 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -703,12 +703,16 @@ impl pallet_staking::Config for Runtime { type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } +parameter_types! { + pub const VoterUpdateMode: pallet_stake_tracker::VoterUpdateMode = pallet_stake_tracker::VoterUpdateMode::Strict; +} + impl pallet_stake_tracker::Config for Runtime { type Currency = Balances; - type RuntimeEvent = RuntimeEvent; type Staking = Staking; type VoterList = VoterList; type TargetList = TargetList; + type VoterUpdateMode = VoterUpdateMode; } impl pallet_fast_unstake::Config for Runtime { diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 23f029c62b94..b63f00129d38 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -1212,7 +1212,7 @@ mod benchmarks { impl_benchmark_test_suite!( Staking, - crate::mock::ExtBuilder::default().has_stakers(true), + crate::mock::ExtBuilder::default().has_stakers(true).set_voter_list_lazy(), crate::mock::Test, exec_name = build_and_execute ); diff --git a/substrate/frame/staking/src/migrations/v13_stake_tracker/tests.rs b/substrate/frame/staking/src/migrations/v13_stake_tracker/tests.rs index a7050b0ece71..c853d4795fea 100644 --- a/substrate/frame/staking/src/migrations/v13_stake_tracker/tests.rs +++ b/substrate/frame/staking/src/migrations/v13_stake_tracker/tests.rs @@ -135,10 +135,12 @@ fn mb_migration_target_list_dangling_validators_works() { } #[test] -fn mb_migration_target_list_bench_works() { +fn mb_migration_target_list_single_step_bench_works() { ExtBuilder::default() .has_stakers(false) .max_winners(1000) + // skip checks as not all the steps are applied. + .stake_tracker_try_state(false) .build_and_execute(|| { // setup: // 1000 validators; diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index be4a7f35e261..637c49b8b2d2 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -362,12 +362,16 @@ impl OnStakingUpdate for EventTracker { } } +parameter_types! { + pub static VoterUpdateMode: pallet_stake_tracker::VoterUpdateMode = pallet_stake_tracker::VoterUpdateMode::Strict; +} + impl pallet_stake_tracker::Config for Test { type Currency = Balances; - type RuntimeEvent = RuntimeEvent; type Staking = Staking; type VoterList = VoterBagsList; type TargetList = TargetBagsList; + type VoterUpdateMode = VoterUpdateMode; } // Disabling threshold for `UpToLimitDisablingStrategy` @@ -568,6 +572,10 @@ impl ExtBuilder { SkipStakeTrackerTryStateCheck::set(!enable); self } + pub fn set_voter_list_lazy(self) -> Self { + VoterUpdateMode::set(pallet_stake_tracker::VoterUpdateMode::Lazy); + self + } pub fn max_winners(self, max: u32) -> Self { MaxWinners::set(max); self @@ -1100,6 +1108,13 @@ macro_rules! assert_session_era { }; } +pub(crate) fn nominators_of(t: &AccountId) -> Vec { + Nominators::::iter() + .filter(|(_, n)| n.targets.contains(&t)) + .map(|(v, _)| v) + .collect::>() +} + pub(crate) fn staking_events() -> Vec> { System::events() .into_iter() diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index d38f53f0ccbf..2b227a05dedd 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -1080,8 +1080,8 @@ impl Pallet { let mut all_targets = Vec::::with_capacity(final_predicted_len as usize); let mut targets_seen = 0; - // target list may contain chilled validators and dangling (i.e. unbonded) targets, filter - // those. + // target list may contain chilled validators, dangling (i.e. unbonded) targets or even + // nominators that have been validators - filter those out. let mut targets_iter = T::TargetList::iter().filter(|t| match Self::status(&t) { Ok(StakerStatus::Idle) | Ok(StakerStatus::Nominator(_)) | Err(_) => false, Ok(StakerStatus::Validator) => true, diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 3b904acb13e2..4b0d700cd8b0 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -1094,8 +1094,6 @@ pub mod pallet { .try_push(UnlockChunk { value, era }) .map_err(|_| Error::::NoMoreChunks)?; }; - // NOTE: ledger must be updated prior to calling `Self::weight_of`. - ledger.update()?; Self::deposit_event(Event::::Unbonded { stash, amount: value }); diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 7416294b03f9..d451cb1954f7 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -728,7 +728,7 @@ fn nominating_and_rewards_should_work() { // 333 is the reward destination for 3. assert_eq_error_rate!( Balances::total_balance(&333), - 2 * payout_for_11 / 9 + 3 * payout_for_21 / 11, + 2 * payout_for_21 / 9 + 3 * payout_for_11 / 11, 2 ); @@ -1291,19 +1291,25 @@ fn bond_extra_works() { #[test] fn bond_extra_controller_bad_state_works() { - ExtBuilder::default().try_state(false).build_and_execute(|| { - assert_eq!(StakingLedger::::get(StakingAccount::Stash(31)).unwrap().stash, 31); + ExtBuilder::default() + .try_state(false) + .stake_tracker_try_state(false) + .build_and_execute(|| { + assert_eq!(StakingLedger::::get(StakingAccount::Stash(31)).unwrap().stash, 31); - // simulate ledger in bad state: the controller 41 is associated to the stash 31 and 41. - Bonded::::insert(31, 41); + // simulate ledger in bad state: the controller 41 is associated to the stash 31 and 41. + Bonded::::insert(31, 41); - // we confirm that the ledger is in bad state: 31 has 41 as controller and when fetching - // the ledger associated with the controller 41, its stash is 41 (and not 31). - assert_eq!(Ledger::::get(41).unwrap().stash, 41); + // we confirm that the ledger is in bad state: 31 has 41 as controller and when fetching + // the ledger associated with the controller 41, its stash is 41 (and not 31). + assert_eq!(Ledger::::get(41).unwrap().stash, 41); - // if the ledger is in this bad state, the `bond_extra` should fail. - assert_noop!(Staking::bond_extra(RuntimeOrigin::signed(31), 10), Error::::BadState); - }) + // if the ledger is in this bad state, the `bond_extra` should fail. + assert_noop!( + Staking::bond_extra(RuntimeOrigin::signed(31), 10), + Error::::BadState + ); + }) } #[test] @@ -3388,8 +3394,8 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid let exposure_11 = Staking::eras_stakers(active_era(), &11); let exposure_21 = Staking::eras_stakers(active_era(), &21); - assert_eq!(exposure_11.total, 1000 + 125); - assert_eq!(exposure_21.total, 1000 + 375); + assert_eq!(exposure_11.total, 1000 + 375); + assert_eq!(exposure_21.total, 1000 + 125); on_offence_now( &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], @@ -3407,12 +3413,12 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid slash_era: 1 }, Event::Slashed { staker: 11, amount: 100 }, - Event::Slashed { staker: 101, amount: 12 }, + Event::Slashed { staker: 101, amount: 37 }, ] ); // post-slash balance - let nominator_slash_amount_11 = 125 / 10; + let nominator_slash_amount_11 = 375 / 10; assert_eq!(Balances::free_balance(11), 900); assert_eq!(Balances::free_balance(101), 2000 - nominator_slash_amount_11); @@ -3427,11 +3433,11 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid let exposure_21 = Staking::eras_stakers(active_era(), &21); // 11's own expo is reduced. sum of support from 11 is less (448), which is 500 - // 900 + 146 - assert!(matches!(exposure_11, Exposure { own: 900, total: 1046, .. })); - // 1000 + 342 - assert!(matches!(exposure_21, Exposure { own: 1000, total: 1342, .. })); - assert_eq!(500 - 146 - 342, nominator_slash_amount_11); + // 900 + 346 + assert!(matches!(exposure_11, Exposure { own: 900, total: 1036, .. })); + // 1000 + 327 + assert!(matches!(exposure_21, Exposure { own: 1000, total: 1327, .. })); + assert_eq!(500 - 136 - 327, nominator_slash_amount_11); }); } @@ -3484,7 +3490,7 @@ fn non_slashable_offence_disables_validator() { slash_era: 1 }, Event::Slashed { staker: 21, amount: 250 }, - Event::Slashed { staker: 101, amount: 94 } + Event::Slashed { staker: 101, amount: 31 } ] ); @@ -3546,7 +3552,7 @@ fn slashing_independent_of_disabling_validator() { slash_era: 1 }, Event::Slashed { staker: 21, amount: 250 }, - Event::Slashed { staker: 101, amount: 94 } + Event::Slashed { staker: 101, amount: 31 } ] ); @@ -7441,8 +7447,6 @@ mod on_staking_update_events { add_slash(&11); ensure_on_staking_updates_emitted(vec![ - // disabling/chilling validator at slash. - ValidatorIdle { who: 11 }, Slash { who: 11, slashed_active: 90, slashed_total: 10 }, // slash applied on the validator stake. StakeUpdate { @@ -7561,6 +7565,7 @@ mod on_staking_update_events { let initial_exposure = Staking::eras_stakers(active_era(), &11); // 101 is a nominator for 11 assert_eq!(initial_exposure.others.first().unwrap().who, 101); + // make 101 a virtual nominator ::migrate_to_virtual_staker(&101); // set payee different to self. @@ -7636,6 +7641,7 @@ mod on_staking_update_events { let initial_exposure = Staking::eras_stakers(active_era(), &11); // 101 is a nominator for 11 assert_eq!(initial_exposure.others.first().unwrap().who, 101); + // make 101 a virtual nominator ::migrate_to_virtual_staker(&101); // set payee different to self. @@ -7676,6 +7682,7 @@ mod on_staking_update_events { // validator can be reaped. assert_ok!(Staking::reap_stash(RuntimeOrigin::signed(10), 11, u32::MAX)); + // nominator is a virtual staker and cannot be reaped. assert_noop!( Staking::reap_stash(RuntimeOrigin::signed(10), 101, u32::MAX), @@ -7720,15 +7727,161 @@ mod stake_tracker { type A = AccountId; #[test] - fn validator_turns_nominator() { + fn duplicate_nominations_dedup_approvals_works() { + ExtBuilder::default() + .add_staker(100, 100, 1_000, StakerStatus::Nominator(vec![11])) + .build_and_execute(|| { + // 11 is a validator. + assert_eq!(Staking::status(&11), Ok(StakerStatus::Validator)); + + // currently, 101 and 100 nominates 11. + assert_eq!(nominators_of(&11), vec![101, 100]); + + // current approvals is the self stake + 101 + 100 active stakes, as expected. + assert_eq!( + TargetBagsList::score(&11), + Staking::active_stake(&11).unwrap() + + Staking::active_stake(&101).unwrap() + + Staking::active_stake(&100).unwrap() + ); + + // change nominations of 101 to nominate 11 with duplicates. + assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![11, 11, 11])); + assert_eq!(nominators_of(&11), vec![101, 100]); + + // even though the nominations of 101 have duplicate targets as 11. + assert_eq!(Nominators::::get(&101).unwrap().targets, vec![11, 11, 11]); + // the approvals stake of 11 is self_stake + active stake of 101 and 100. duplicate + // nominations in the same voter is dedup by the stake tracker. + assert_eq!( + TargetBagsList::score(&11), + Staking::active_stake(&11).unwrap() + + Staking::active_stake(&101).unwrap() + + Staking::active_stake(&100).unwrap() + ); + + // when bonding extra, the dedup nominations are also not counted in the approvals. + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(101), 500)); + assert_eq!( + TargetBagsList::score(&11), + Staking::active_stake(&11).unwrap() + + Staking::active_stake(&101).unwrap() + + Staking::active_stake(&100).unwrap() + ); + + // same when nominator with duplicate nominations unbonds stake. + assert_ok!(Staking::chill(RuntimeOrigin::signed(101))); + assert_eq!(Staking::status(&101), Ok(StakerStatus::Idle)); + + // 101 is not nominator of 11 anymore. + assert_eq!(nominators_of(&11), vec![100]); + assert_eq!( + TargetBagsList::score(&11), + Staking::active_stake(&11).unwrap() + Staking::active_stake(&100).unwrap() + ); + }) + } + + #[test] + fn total_slash_and_reap_target_works() { + // Test case: a validator and *ALL* its nominators are 100% slashed. The target score drops + // to 0. After calling reap_stash on the validator, the target list node is removed and *NO* + // nominations are left behind. + ExtBuilder::default() + // we need enough validators such that disables are allowed. + .validator_count(7) + .set_status(41, StakerStatus::Validator) + .set_status(51, StakerStatus::Validator) + .set_status(201, StakerStatus::Validator) + .set_status(202, StakerStatus::Validator) + .build_and_execute(|| { + // make 101 only nominate 11. + assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![11])); + mock::start_active_era(1); + + // slash all stake. + let slash_percent = Perbill::from_percent(100); + let initial_exposure = Staking::eras_stakers(active_era(), &11); + // 101 is a nominator for 11 + assert_eq!(initial_exposure.others.first().unwrap().who, 101); + + let validator_balance = Balances::free_balance(&11); + let validator_stake = Staking::ledger(11.into()).unwrap().total; + + // target score is as expected. + assert_eq!( + TargetBagsList::score(&11), + Staking::active_stake(&11).unwrap() + Staking::active_stake(&101).unwrap() + ); + + on_offence_now( + &[OffenceDetails { + offender: (11, initial_exposure.clone()), + reporters: vec![], + }], + &[slash_percent], + ); + + // both stakes must have been decreased to 0. + assert_eq!(Staking::ledger(101.into()).unwrap().active, 0); + assert_eq!(Staking::ledger(11.into()).unwrap().active, 0); + + // all validator stake is slashed + assert_eq_error_rate!( + validator_balance - validator_stake, + Balances::free_balance(&11), + 1 + ); + assert!(is_disabled(11)); + + // target score is decreased as expected, down to 0. + assert_eq!(TargetBagsList::score(&11), 0); + assert_eq!( + TargetBagsList::score(&11), + Staking::active_stake(&11).unwrap() + Staking::active_stake(&101).unwrap() + ); + + // since the active take of the nominator dropped to 0 after the slash,, it has been + // chilled. + assert_eq!(Staking::status(&101), Ok(StakerStatus::Idle)); + + // reap validator stash works. + assert_ok!(Staking::reap_stash(RuntimeOrigin::signed(10), 11, u32::MAX)); + + // 11 is not a nominator/validator anymore. + assert!(Staking::status(&11).is_err()); + assert_eq!(TargetBagsList::score(&11), 0); + }) + } + + #[test] + fn virtual_nomination_works() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(Staking::status(&11), Ok(StakerStatus::Validator)); + // make 101 only nominate 11. + assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![11])); - // 11 and 21 are both Validators. - assert_ok!(Staking::nominate(RuntimeOrigin::signed(11), vec![21])); + // make 101 a virtual nominator + ::migrate_to_virtual_staker(&101); + assert_ok!(::update_payee(&101, &102)); + assert_eq!(Pallet::::is_virtual_staker(&101), true); - assert_eq!(Staking::status(&11), Ok(StakerStatus::Nominator(vec![21]))); - }); + // as a virtual nominator, 101 nominates 31 too. + assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![11, 31])); + + let stake_101 = Staking::active_stake(&101).unwrap(); + + // target score of 11 is the sum of self stake and 101 bond. + assert_eq!(TargetBagsList::score(&11), Staking::active_stake(&11).unwrap() + stake_101); + // same for 31. + assert_eq!(TargetBagsList::score(&31), Staking::active_stake(&31).unwrap() + stake_101); + + assert_ok!(Staking::chill(RuntimeOrigin::signed(101))); + assert_ok!(Staking::kill_stash(&101, 0)); + + // target scores updated once virtual nominator unbonded. + assert_eq!(TargetBagsList::score(&11), Staking::active_stake(&11).unwrap()); + assert_eq!(TargetBagsList::score(&31), Staking::active_stake(&31).unwrap()); + }) } #[test] @@ -8028,10 +8181,10 @@ mod stake_tracker { [ BagsEvent::Rebagged { who: 11, from: 1000, to: 100 }, BagsEvent::ScoreUpdated { who: 11, new_score: 0 }, - BagsEvent::Rebagged { who: 21, from: 1000, to: 10000 }, - BagsEvent::ScoreUpdated { who: 21, new_score: 2100 }, BagsEvent::Rebagged { who: 11, from: 100, to: 2000 }, BagsEvent::ScoreUpdated { who: 11, new_score: 1100 }, + BagsEvent::Rebagged { who: 21, from: 1000, to: 10000 }, + BagsEvent::ScoreUpdated { who: 21, new_score: 2100 }, ] ); @@ -8157,7 +8310,7 @@ mod stake_tracker { System::reset_events(); // checks the current targets' score and list sorting. - assert_eq!(voters_and_targets().1, [(21, 2050), (11, 2050), (31, 500)]); + assert_eq!(voters_and_targets().1, [(11, 2050), (21, 2050), (31, 500)]); // get the bonded stake of the nominators that will be affected by the slash. let stake_101_before = Staking::ledger(Stash(101)).unwrap().active; @@ -8200,7 +8353,7 @@ mod stake_tracker { assert_eq!( score_11_after, score_11_before - - self_stake_11_before - (slash_percent * total_others_stake_to_slash), + slash_percent * (self_stake_11_before + total_others_stake_to_slash), ); // self-stake of 11 has decreased by 50% due to slash. @@ -8226,35 +8379,70 @@ mod stake_tracker { ); // the target list has been updated accordingly and an indirect rebag of 21 happened. - // Although 11 is chilled, it is still part of the target list. assert_eq!( voters_and_targets().1, - [(21, score_21_after), (11, score_11_after), (31, 500)] + [(11, score_11_after), (21, score_21_after), (31, 500)] ); assert_eq!( target_bags_events(), [ BagsEvent::Rebagged { who: 11, from: 10000, to: 2000 }, - BagsEvent::ScoreUpdated { who: 11, new_score: 1050 }, - BagsEvent::Rebagged { who: 11, from: 2000, to: 1000 }, - BagsEvent::ScoreUpdated { who: 11, new_score: 965 }, + BagsEvent::ScoreUpdated { who: 11, new_score: 1550 }, + BagsEvent::ScoreUpdated { who: 11, new_score: 1385 }, BagsEvent::Rebagged { who: 21, from: 10000, to: 2000 }, - BagsEvent::ScoreUpdated { who: 21, new_score: 1965 }, - BagsEvent::ScoreUpdated { who: 21, new_score: 1872 }, - BagsEvent::ScoreUpdated { who: 11, new_score: 872 }, + BagsEvent::ScoreUpdated { who: 21, new_score: 1885 }, + BagsEvent::ScoreUpdated { who: 11, new_score: 1204 }, + BagsEvent::ScoreUpdated { who: 21, new_score: 1704 } ] ); - // fetching targets sorted and filtered by status works. + // fetching targets sorted and filtered by status works. Validators are not chilled + // upon slashing, thus 11 is part of the set. assert_eq!( TargetBagsList::iter() .filter(|t| Staking::status(&t).unwrap() != StakerStatus::Idle) .collect::>(), - [21, 31], + [11, 21, 31], ); }) } + #[test] + fn validator_nominator_roles_snip_snap_works() { + // Test case: Validator becomes a nominator and vice-versa. In the process of a nominator + // turning validator, it leaves a target behind even though the staker is a nominator. The + // target is removed from the list once its score drops to 0 (similar to the dangling + // targets). + ExtBuilder::default().build_and_execute(|| { + // 11 and 21 are both validators. + assert_eq!(Staking::status(&11), Ok(StakerStatus::Validator)); + assert_eq!(Staking::status(&21), Ok(StakerStatus::Validator)); + // 101 nominates 11 and 21. + assert_eq!(Staking::status(&101), Ok(StakerStatus::Nominator(vec![11, 21]))); + + // 11 becomes nominator. + assert_ok!(Staking::nominate(RuntimeOrigin::signed(11), vec![21])); + assert_eq!(Staking::status(&11), Ok(StakerStatus::Nominator(vec![21]))); + + // however, since 101 nominates 11 (which now is a nominator), 11 is still in the + // target list even though it is a nominator. + assert_eq!(nominators_of(&11), vec![101]); + assert_eq!(TargetBagsList::score(&11), Staking::active_stake(&101).unwrap()); + + // 101 starts validating. thus, it chills as a nominator. + assert_ok!(Staking::validate(RuntimeOrigin::signed(101), Default::default())); + assert_eq!(Staking::status(&101), Ok(StakerStatus::Validator)); + + // now 11 has no more score in the target list and it is a nominator, thus it is + // removed from the target list. + assert_eq!(nominators_of(&11).len(), 0); + assert!(!TargetBagsList::contains(&11)); + + // stake-tracker try-state will ensure actions above keep the integrity of the target + // and voter list. + }); + } + #[test] fn no_redundant_update_ledger_events() { ExtBuilder::default().build_and_execute(|| { @@ -8292,81 +8480,91 @@ mod stake_tracker { #[test] fn drop_dangling_nomination_works() { - ExtBuilder::default().try_state(false).build_and_execute(|| { - // setup. - bond_validator(42, 10); + ExtBuilder::default() + .try_state(false) + .stake_tracker_try_state(false) + .build_and_execute(|| { + // setup. + bond_validator(42, 10); - assert_ok!(Staking::bond(RuntimeOrigin::signed(90), 500, RewardDestination::Staked)); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(90), vec![11])); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![11])); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(90), + 500, + RewardDestination::Staked + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(90), vec![11])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![11])); - // target is dangling with nominations from 101 and 90. - setup_dangling_target_for_nominators(42, vec![101, 90]); + // target is dangling with nominations from 101 and 90. + setup_dangling_target_for_nominators(42, vec![101, 90]); - // 101 is now nominating 42.. - assert_ok!(Staking::status(&101), StakerStatus::Nominator(vec![11, 42])); - // .. which is unbonded.. - assert!(Staking::status(&42).is_err()); - // .. and still part of the target list (thus dangling). - assert!(TargetBagsList::contains(&42)); + // 101 is now nominating 42.. + assert_ok!(Staking::status(&101), StakerStatus::Nominator(vec![11, 42])); + // .. which is unbonded.. + assert!(Staking::status(&42).is_err()); + // .. and still part of the target list (thus dangling). + assert!(TargetBagsList::contains(&42)); - // remove 90 as dangling nomination. - assert_ok!(Staking::drop_dangling_nomination(RuntimeOrigin::signed(1), 90, 42)); + // remove 90 as dangling nomination. + assert_ok!(Staking::drop_dangling_nomination(RuntimeOrigin::signed(1), 90, 42)); - assert_eq!( - *mock::staking_events().last().unwrap(), - Event::::DanglingNominationDropped { nominator: 90, target: 42 }.into(), - ); + assert_eq!( + *mock::staking_events().last().unwrap(), + Event::::DanglingNominationDropped { nominator: 90, target: 42 }.into(), + ); - // now, 90 is not nominating 42 anymore. - assert_ok!(Staking::status(&90), StakerStatus::Nominator(vec![11])); - // but 42 is still dangling because 101 is still nominating it - assert!(TargetBagsList::contains(&42)); - assert_ok!(Staking::status(&101), StakerStatus::Nominator(vec![11, 42])); + // now, 90 is not nominating 42 anymore. + assert_ok!(Staking::status(&90), StakerStatus::Nominator(vec![11])); + // but 42 is still dangling because 101 is still nominating it + assert!(TargetBagsList::contains(&42)); + assert_ok!(Staking::status(&101), StakerStatus::Nominator(vec![11, 42])); - // when the last dangling nomination is removed, the danling target is removed. - assert_ok!(Staking::drop_dangling_nomination(RuntimeOrigin::signed(1), 101, 42)); + // when the last dangling nomination is removed, the danling target is removed. + assert_ok!(Staking::drop_dangling_nomination(RuntimeOrigin::signed(1), 101, 42)); - assert_ok!(Staking::status(&101), StakerStatus::Nominator(vec![11])); - assert!(!TargetBagsList::contains(&42)); + assert_ok!(Staking::status(&101), StakerStatus::Nominator(vec![11])); + assert!(!TargetBagsList::contains(&42)); - assert_eq!( - *mock::staking_events().last().unwrap(), - Event::::DanglingNominationDropped { nominator: 101, target: 42 }.into(), - ); - }) + assert_eq!( + *mock::staking_events().last().unwrap(), + Event::::DanglingNominationDropped { nominator: 101, target: 42 }.into(), + ); + }) } #[test] fn drop_dangling_nomination_failures_work() { - ExtBuilder::default().try_state(false).build_and_execute(|| { - // target is not dangling since it does not exist in the target list. - assert!(Staking::status(&42).is_err()); - assert!(!TargetBagsList::contains(&42)); + ExtBuilder::default() + .try_state(false) + .stake_tracker_try_state(false) + .build_and_execute(|| { + // target is not dangling since it does not exist in the target list. + assert!(Staking::status(&42).is_err()); + assert!(!TargetBagsList::contains(&42)); - assert_noop!( - Staking::drop_dangling_nomination(RuntimeOrigin::signed(1), 10, 42), - Error::::NotDanglingTarget, - ); + assert_noop!( + Staking::drop_dangling_nomination(RuntimeOrigin::signed(1), 10, 42), + Error::::NotDanglingTarget, + ); - // target is not dangling since it is still bonded. - assert_eq!(Staking::status(&31), Ok(StakerStatus::Validator)); - assert_noop!( - Staking::drop_dangling_nomination(RuntimeOrigin::signed(1), 42, 10), - Error::::NotDanglingTarget, - ); + // target is not dangling since it is still bonded. + assert_eq!(Staking::status(&31), Ok(StakerStatus::Validator)); + assert_noop!( + Staking::drop_dangling_nomination(RuntimeOrigin::signed(1), 42, 10), + Error::::NotDanglingTarget, + ); - // target is dangling but voter is not nominating it. - bond_validator(42, 10); + // target is dangling but voter is not nominating it. + bond_validator(42, 10); - assert_eq!(Staking::status(&101), Ok(StakerStatus::Nominator(vec![11, 21]))); - setup_dangling_target_for_nominators(42, vec![101]); - assert!(Staking::status(&42).is_err()); - assert_noop!( - Staking::drop_dangling_nomination(RuntimeOrigin::signed(1), 11, 42), - Error::::NotNominator, - ); - }) + assert_eq!(Staking::status(&101), Ok(StakerStatus::Nominator(vec![11, 21]))); + setup_dangling_target_for_nominators(42, vec![101]); + assert!(Staking::status(&42).is_err()); + assert_noop!( + Staking::drop_dangling_nomination(RuntimeOrigin::signed(1), 11, 42), + Error::::NotNominator, + ); + }) } } diff --git a/substrate/frame/staking/src/weights.rs b/substrate/frame/staking/src/weights.rs index c9e138b96a7d..b2617c57dfc3 100644 --- a/substrate/frame/staking/src/weights.rs +++ b/substrate/frame/staking/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for `pallet_staking` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-05-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `runner-unxyhko3-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` @@ -108,8 +108,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1134` // Estimated: `4764` - // Minimum execution time: 61_142_000 picoseconds. - Weight::from_parts(62_583_000, 4764) + // Minimum execution time: 61_346_000 picoseconds. + Weight::from_parts(63_986_000, 4764) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -137,8 +137,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2742` // Estimated: `11506` - // Minimum execution time: 133_409_000 picoseconds. - Weight::from_parts(137_789_000, 11506) + // Minimum execution time: 133_115_000 picoseconds. + Weight::from_parts(137_389_000, 11506) .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(8_u64)) } @@ -170,8 +170,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2738` // Estimated: `8877` - // Minimum execution time: 128_838_000 picoseconds. - Weight::from_parts(132_727_000, 8877) + // Minimum execution time: 130_115_000 picoseconds. + Weight::from_parts(133_942_000, 8877) .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().writes(8_u64)) } @@ -198,10 +198,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1415` // Estimated: `4764` - // Minimum execution time: 62_632_000 picoseconds. - Weight::from_parts(64_185_798, 4764) - // Standard Error: 1_112 - .saturating_add(Weight::from_parts(47_715, 0).saturating_mul(s.into())) + // Minimum execution time: 61_773_000 picoseconds. + Weight::from_parts(64_531_144, 4764) + // Standard Error: 1_573 + .saturating_add(Weight::from_parts(53_571, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -240,12 +240,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2843 + s * (4 ±0)` + // Measured: `2842 + s * (4 ±0)` // Estimated: `8877 + s * (4 ±0)` - // Minimum execution time: 135_885_000 picoseconds. - Weight::from_parts(147_555_024, 8877) - // Standard Error: 4_447 - .saturating_add(Weight::from_parts(1_474_964, 0).saturating_mul(s.into())) + // Minimum execution time: 134_037_000 picoseconds. + Weight::from_parts(145_678_976, 8877) + // Standard Error: 5_183 + .saturating_add(Weight::from_parts(1_449_480, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(17_u64)) .saturating_add(T::DbWeight::get().writes(15_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -283,8 +283,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1644` // Estimated: `4556` - // Minimum execution time: 79_115_000 picoseconds. - Weight::from_parts(82_034_000, 4556) + // Minimum execution time: 79_658_000 picoseconds. + Weight::from_parts(81_457_000, 4556) .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().writes(9_u64)) } @@ -298,20 +298,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `TargetList::ListNodes` (r:3 w:3) /// Proof: `TargetList::ListNodes` (`max_values`: None, `max_size`: Some(170), added: 2645, mode: `MaxEncodedLen`) - /// Storage: `TargetList::ListBags` (r:49 w:49) + /// Storage: `TargetList::ListBags` (r:12 w:12) /// Proof: `TargetList::ListBags` (`max_values`: None, `max_size`: Some(90), added: 2565, mode: `MaxEncodedLen`) /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `3671 + k * (752 ±0)` - // Estimated: `33920 + k * (3566 ±0)` - // Minimum execution time: 99_428_000 picoseconds. - Weight::from_parts(172_946_107, 33920) - // Standard Error: 43_151 - .saturating_add(Weight::from_parts(37_545_879, 0).saturating_mul(k.into())) - .saturating_add(T::DbWeight::get().reads(18_u64)) + // Measured: `3103 + k * (757 ±0)` + // Estimated: `6120 + k * (3566 ±0)` + // Minimum execution time: 78_476_000 picoseconds. + Weight::from_parts(2_780_311, 6120) + // Standard Error: 55_789 + .saturating_add(Weight::from_parts(35_357_040, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(k.into()))) - .saturating_add(T::DbWeight::get().writes(16_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) .saturating_add(Weight::from_parts(0, 3566).saturating_mul(k.into())) } @@ -344,12 +344,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2384 + n * (348 ±0)` + // Measured: `2350 + n * (348 ±0)` // Estimated: `6248 + n * (3033 ±0)` - // Minimum execution time: 112_661_000 picoseconds. - Weight::from_parts(90_042_831, 6248) - // Standard Error: 36_011 - .saturating_add(Weight::from_parts(33_478_365, 0).saturating_mul(n.into())) + // Minimum execution time: 114_109_000 picoseconds. + Weight::from_parts(92_374_250, 6248) + // Standard Error: 40_918 + .saturating_add(Weight::from_parts(32_671_582, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(8_u64)) @@ -376,10 +376,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill() -> Weight { // Proof Size summary in bytes: - // Measured: `2430` + // Measured: `2429` // Estimated: `8877` - // Minimum execution time: 95_934_000 picoseconds. - Weight::from_parts(97_624_000, 8877) + // Minimum execution time: 94_864_000 picoseconds. + Weight::from_parts(97_414_000, 8877) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(9_u64)) } @@ -393,8 +393,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `935` // Estimated: `4556` - // Minimum execution time: 19_872_000 picoseconds. - Weight::from_parts(20_614_000, 4556) + // Minimum execution time: 20_223_000 picoseconds. + Weight::from_parts(20_997_000, 4556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -408,8 +408,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1002` // Estimated: `4556` - // Minimum execution time: 23_905_000 picoseconds. - Weight::from_parts(24_901_000, 4556) + // Minimum execution time: 24_225_000 picoseconds. + Weight::from_parts(24_960_000, 4556) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -421,8 +421,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `935` // Estimated: `8122` - // Minimum execution time: 23_846_000 picoseconds. - Weight::from_parts(24_732_000, 8122) + // Minimum execution time: 24_007_000 picoseconds. + Weight::from_parts(24_897_000, 8122) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -432,8 +432,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_438_000 picoseconds. - Weight::from_parts(2_597_000, 0) + // Minimum execution time: 2_497_000 picoseconds. + Weight::from_parts(2_626_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -442,8 +442,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_219_000 picoseconds. - Weight::from_parts(8_685_000, 0) + // Minimum execution time: 7_896_000 picoseconds. + Weight::from_parts(8_402_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -452,8 +452,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_212_000 picoseconds. - Weight::from_parts(8_703_000, 0) + // Minimum execution time: 8_122_000 picoseconds. + Weight::from_parts(8_432_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -462,8 +462,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_193_000 picoseconds. - Weight::from_parts(8_622_000, 0) + // Minimum execution time: 8_027_000 picoseconds. + Weight::from_parts(8_393_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::Invulnerables` (r:0 w:1) @@ -473,10 +473,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_673_000 picoseconds. - Weight::from_parts(3_260_136, 0) + // Minimum execution time: 2_616_000 picoseconds. + Weight::from_parts(3_281_227, 0) // Standard Error: 32 - .saturating_add(Weight::from_parts(10_059, 0).saturating_mul(v.into())) + .saturating_add(Weight::from_parts(10_003, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::Ledger` (r:11800 w:11800) @@ -490,10 +490,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1779 + i * (229 ±0)` // Estimated: `990 + i * (7132 ±0)` - // Minimum execution time: 5_081_000 picoseconds. - Weight::from_parts(5_272_000, 990) - // Standard Error: 106_956 - .saturating_add(Weight::from_parts(30_736_628, 0).saturating_mul(i.into())) + // Minimum execution time: 4_905_000 picoseconds. + Weight::from_parts(5_156_000, 990) + // Standard Error: 83_880 + .saturating_add(Weight::from_parts(30_406_155, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 7132).saturating_mul(i.into())) @@ -533,12 +533,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2843 + s * (4 ±0)` + // Measured: `2842 + s * (4 ±0)` // Estimated: `8877 + s * (4 ±0)` - // Minimum execution time: 131_018_000 picoseconds. - Weight::from_parts(144_246_801, 8877) - // Standard Error: 4_444 - .saturating_add(Weight::from_parts(1_458_960, 0).saturating_mul(s.into())) + // Minimum execution time: 130_799_000 picoseconds. + Weight::from_parts(142_508_520, 8877) + // Standard Error: 4_715 + .saturating_add(Weight::from_parts(1_440_259, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(17_u64)) .saturating_add(T::DbWeight::get().writes(16_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -551,10 +551,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `66705` // Estimated: `70170` - // Minimum execution time: 96_853_000 picoseconds. - Weight::from_parts(892_001_373, 70170) - // Standard Error: 57_528 - .saturating_add(Weight::from_parts(4_872_094, 0).saturating_mul(s.into())) + // Minimum execution time: 107_433_000 picoseconds. + Weight::from_parts(1_164_974_155, 70170) + // Standard Error: 76_453 + .saturating_add(Weight::from_parts(6_498_608, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -594,7 +594,7 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) /// Storage: `TargetList::ListNodes` (r:1 w:1) /// Proof: `TargetList::ListNodes` (`max_values`: None, `max_size`: Some(170), added: 2645, mode: `MaxEncodedLen`) - /// Storage: `TargetList::ListBags` (r:8 w:8) + /// Storage: `TargetList::ListBags` (r:13 w:13) /// Proof: `TargetList::ListBags` (`max_values`: None, `max_size`: Some(90), added: 2565, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:257 w:257) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) @@ -603,15 +603,15 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `34118 + n * (625 ±0)` - // Estimated: `31763 + n * (3774 ±0)` - // Minimum execution time: 248_954_000 picoseconds. - Weight::from_parts(1_054_655_035, 31763) - // Standard Error: 540_385 - .saturating_add(Weight::from_parts(86_501_516, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(29_u64)) + // Measured: `34145 + n * (625 ±0)` + // Estimated: `31768 + n * (3774 ±3)` + // Minimum execution time: 256_919_000 picoseconds. + Weight::from_parts(1_131_187_427, 31768) + // Standard Error: 504_022 + .saturating_add(Weight::from_parts(84_568_885, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(34_u64)) .saturating_add(T::DbWeight::get().reads((10_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(16_u64)) + .saturating_add(T::DbWeight::get().writes(21_u64)) .saturating_add(T::DbWeight::get().writes((4_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into())) } @@ -640,13 +640,15 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2743 + l * (7 ±0)` // Estimated: `11506` - // Minimum execution time: 130_367_000 picoseconds. - Weight::from_parts(134_369_938, 11506) - // Standard Error: 6_686 - .saturating_add(Weight::from_parts(185_942, 0).saturating_mul(l.into())) + // Minimum execution time: 129_295_000 picoseconds. + Weight::from_parts(134_342_502, 11506) + // Standard Error: 6_141 + .saturating_add(Weight::from_parts(203_335, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(8_u64)) } + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) @@ -667,8 +669,6 @@ impl WeightInfo for SubstrateWeight { /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::CounterForListNodes` (r:1 w:1) /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::VirtualStakers` (r:1 w:1) - /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -680,12 +680,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2843 + s * (4 ±0)` + // Measured: `2842 + s * (4 ±0)` // Estimated: `8877 + s * (4 ±0)` - // Minimum execution time: 142_165_000 picoseconds. - Weight::from_parts(150_545_357, 8877) - // Standard Error: 4_445 - .saturating_add(Weight::from_parts(1_439_599, 0).saturating_mul(s.into())) + // Minimum execution time: 142_841_000 picoseconds. + Weight::from_parts(151_373_760, 8877) + // Standard Error: 4_909 + .saturating_add(Weight::from_parts(1_432_397, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(16_u64)) .saturating_add(T::DbWeight::get().writes(15_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -735,12 +735,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0 + n * (720 ±0) + v * (3776 ±0)` // Estimated: `516555 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 957_678_000 picoseconds. - Weight::from_parts(964_225_000, 516555) - // Standard Error: 2_413_573 - .saturating_add(Weight::from_parts(79_822_457, 0).saturating_mul(v.into())) - // Standard Error: 240_499 - .saturating_add(Weight::from_parts(20_562_885, 0).saturating_mul(n.into())) + // Minimum execution time: 963_499_000 picoseconds. + Weight::from_parts(966_544_000, 516555) + // Standard Error: 2_158_092 + .saturating_add(Weight::from_parts(72_956_282, 0).saturating_mul(v.into())) + // Standard Error: 215_042 + .saturating_add(Weight::from_parts(19_482_545, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(410_u64)) .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -771,12 +771,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `3208 + n * (911 ±0) + v * (395 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 37_903_494_000 picoseconds. - Weight::from_parts(38_991_701_000, 512390) - // Standard Error: 406_095 - .saturating_add(Weight::from_parts(8_454_350, 0).saturating_mul(v.into())) - // Standard Error: 406_095 - .saturating_add(Weight::from_parts(2_716_681, 0).saturating_mul(n.into())) + // Minimum execution time: 36_355_465_000 picoseconds. + Weight::from_parts(36_660_578_000, 512390) + // Standard Error: 422_770 + .saturating_add(Weight::from_parts(3_407_216, 0).saturating_mul(v.into())) + // Standard Error: 422_770 + .saturating_add(Weight::from_parts(6_173_669, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(201_u64)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -799,12 +799,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `58454 + v * (323 ±0)` + // Measured: `58129 + v * (323 ±0)` // Estimated: `516555 + v * (3033 ±0)` - // Minimum execution time: 9_230_123_000 picoseconds. - Weight::from_parts(1_671_253_694, 516555) - // Standard Error: 178_631 - .saturating_add(Weight::from_parts(17_181_912, 0).saturating_mul(v.into())) + // Minimum execution time: 8_890_124_000 picoseconds. + Weight::from_parts(9_016_030_000, 516555) + // Standard Error: 111_852 + .saturating_add(Weight::from_parts(7_039_233, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(206_u64)) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 3033).saturating_mul(v.into())) @@ -827,8 +827,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_848_000 picoseconds. - Weight::from_parts(6_161_000, 0) + // Minimum execution time: 5_627_000 picoseconds. + Weight::from_parts(6_134_000, 0) .saturating_add(T::DbWeight::get().writes(7_u64)) } /// Storage: `Staking::MinCommission` (r:0 w:1) @@ -849,8 +849,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_778_000 picoseconds. - Weight::from_parts(5_052_000, 0) + // Minimum execution time: 4_901_000 picoseconds. + Weight::from_parts(5_209_000, 0) .saturating_add(T::DbWeight::get().writes(7_u64)) } /// Storage: `Staking::Bonded` (r:1 w:0) @@ -879,10 +879,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill_other() -> Weight { // Proof Size summary in bytes: - // Measured: `2545` + // Measured: `2544` // Estimated: `8877` - // Minimum execution time: 109_628_000 picoseconds. - Weight::from_parts(111_707_000, 8877) + // Minimum execution time: 111_325_000 picoseconds. + Weight::from_parts(114_102_000, 8877) .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().writes(9_u64)) } @@ -894,8 +894,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `724` // Estimated: `3510` - // Minimum execution time: 12_843_000 picoseconds. - Weight::from_parts(13_310_000, 3510) + // Minimum execution time: 13_264_000 picoseconds. + Weight::from_parts(13_696_000, 3510) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -905,10 +905,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_051_000 picoseconds. - Weight::from_parts(3_169_000, 0) + // Minimum execution time: 3_052_000 picoseconds. + Weight::from_parts(3_242_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -917,8 +919,6 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Staking::VirtualStakers` (r:1 w:0) - /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) @@ -929,14 +929,14 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1165` // Estimated: `4764` - // Minimum execution time: 56_877_000 picoseconds. - Weight::from_parts(58_435_000, 4764) + // Minimum execution time: 61_779_000 picoseconds. + Weight::from_parts(62_484_000, 4764) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Staking::Bonded` (r:3 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `TargetList::ListNodes` (r:1 w:1) + /// Storage: `TargetList::ListNodes` (r:2 w:2) /// Proof: `TargetList::ListNodes` (`max_values`: None, `max_size`: Some(170), added: 2645, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:2 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) @@ -954,12 +954,12 @@ impl WeightInfo for SubstrateWeight { /// Proof: `TargetList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn drop_dangling_nomination() -> Weight { // Proof Size summary in bytes: - // Measured: `1863` + // Measured: `1973` // Estimated: `8631` - // Minimum execution time: 93_338_000 picoseconds. - Weight::from_parts(94_432_000, 8631) - .saturating_add(T::DbWeight::get().reads(13_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Minimum execution time: 101_638_000 picoseconds. + Weight::from_parts(103_488_000, 8631) + .saturating_add(T::DbWeight::get().reads(14_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Staking::Nominators` (r:6 w:0) /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) @@ -967,20 +967,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:21 w:0) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `TargetList::ListNodes` (r:78 w:78) + /// Storage: `TargetList::ListNodes` (r:180 w:180) /// Proof: `TargetList::ListNodes` (`max_values`: None, `max_size`: Some(170), added: 2645, mode: `MaxEncodedLen`) - /// Storage: `TargetList::ListBags` (r:4 w:4) + /// Storage: `TargetList::ListBags` (r:2 w:2) /// Proof: `TargetList::ListBags` (`max_values`: None, `max_size`: Some(90), added: 2565, mode: `MaxEncodedLen`) /// Storage: `TargetList::CounterForListNodes` (r:1 w:1) /// Proof: `TargetList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn v13_mmb_step() -> Weight { // Proof Size summary in bytes: - // Measured: `47878` - // Estimated: `207300` - // Minimum execution time: 2_239_356_000 picoseconds. - Weight::from_parts(2_284_677_000, 207300) - .saturating_add(T::DbWeight::get().reads(131_u64)) - .saturating_add(T::DbWeight::get().writes(83_u64)) + // Measured: `70141` + // Estimated: `477090` + // Minimum execution time: 2_703_732_000 picoseconds. + Weight::from_parts(2_779_206_000, 477090) + .saturating_add(T::DbWeight::get().reads(231_u64)) + .saturating_add(T::DbWeight::get().writes(183_u64)) } } @@ -1006,8 +1006,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1134` // Estimated: `4764` - // Minimum execution time: 61_142_000 picoseconds. - Weight::from_parts(62_583_000, 4764) + // Minimum execution time: 61_346_000 picoseconds. + Weight::from_parts(63_986_000, 4764) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1035,8 +1035,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2742` // Estimated: `11506` - // Minimum execution time: 133_409_000 picoseconds. - Weight::from_parts(137_789_000, 11506) + // Minimum execution time: 133_115_000 picoseconds. + Weight::from_parts(137_389_000, 11506) .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(8_u64)) } @@ -1068,8 +1068,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2738` // Estimated: `8877` - // Minimum execution time: 128_838_000 picoseconds. - Weight::from_parts(132_727_000, 8877) + // Minimum execution time: 130_115_000 picoseconds. + Weight::from_parts(133_942_000, 8877) .saturating_add(RocksDbWeight::get().reads(15_u64)) .saturating_add(RocksDbWeight::get().writes(8_u64)) } @@ -1096,10 +1096,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1415` // Estimated: `4764` - // Minimum execution time: 62_632_000 picoseconds. - Weight::from_parts(64_185_798, 4764) - // Standard Error: 1_112 - .saturating_add(Weight::from_parts(47_715, 0).saturating_mul(s.into())) + // Minimum execution time: 61_773_000 picoseconds. + Weight::from_parts(64_531_144, 4764) + // Standard Error: 1_573 + .saturating_add(Weight::from_parts(53_571, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1138,12 +1138,12 @@ impl WeightInfo for () { /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2843 + s * (4 ±0)` + // Measured: `2842 + s * (4 ±0)` // Estimated: `8877 + s * (4 ±0)` - // Minimum execution time: 135_885_000 picoseconds. - Weight::from_parts(147_555_024, 8877) - // Standard Error: 4_447 - .saturating_add(Weight::from_parts(1_474_964, 0).saturating_mul(s.into())) + // Minimum execution time: 134_037_000 picoseconds. + Weight::from_parts(145_678_976, 8877) + // Standard Error: 5_183 + .saturating_add(Weight::from_parts(1_449_480, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(17_u64)) .saturating_add(RocksDbWeight::get().writes(15_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -1181,8 +1181,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1644` // Estimated: `4556` - // Minimum execution time: 79_115_000 picoseconds. - Weight::from_parts(82_034_000, 4556) + // Minimum execution time: 79_658_000 picoseconds. + Weight::from_parts(81_457_000, 4556) .saturating_add(RocksDbWeight::get().reads(14_u64)) .saturating_add(RocksDbWeight::get().writes(9_u64)) } @@ -1196,20 +1196,20 @@ impl WeightInfo for () { /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `TargetList::ListNodes` (r:3 w:3) /// Proof: `TargetList::ListNodes` (`max_values`: None, `max_size`: Some(170), added: 2645, mode: `MaxEncodedLen`) - /// Storage: `TargetList::ListBags` (r:49 w:49) + /// Storage: `TargetList::ListBags` (r:12 w:12) /// Proof: `TargetList::ListBags` (`max_values`: None, `max_size`: Some(90), added: 2565, mode: `MaxEncodedLen`) /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `3671 + k * (752 ±0)` - // Estimated: `33920 + k * (3566 ±0)` - // Minimum execution time: 99_428_000 picoseconds. - Weight::from_parts(172_946_107, 33920) - // Standard Error: 43_151 - .saturating_add(Weight::from_parts(37_545_879, 0).saturating_mul(k.into())) - .saturating_add(RocksDbWeight::get().reads(18_u64)) + // Measured: `3103 + k * (757 ±0)` + // Estimated: `6120 + k * (3566 ±0)` + // Minimum execution time: 78_476_000 picoseconds. + Weight::from_parts(2_780_311, 6120) + // Standard Error: 55_789 + .saturating_add(Weight::from_parts(35_357_040, 0).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(k.into()))) - .saturating_add(RocksDbWeight::get().writes(16_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) .saturating_add(Weight::from_parts(0, 3566).saturating_mul(k.into())) } @@ -1242,12 +1242,12 @@ impl WeightInfo for () { /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2384 + n * (348 ±0)` + // Measured: `2350 + n * (348 ±0)` // Estimated: `6248 + n * (3033 ±0)` - // Minimum execution time: 112_661_000 picoseconds. - Weight::from_parts(90_042_831, 6248) - // Standard Error: 36_011 - .saturating_add(Weight::from_parts(33_478_365, 0).saturating_mul(n.into())) + // Minimum execution time: 114_109_000 picoseconds. + Weight::from_parts(92_374_250, 6248) + // Standard Error: 40_918 + .saturating_add(Weight::from_parts(32_671_582, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(14_u64)) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(8_u64)) @@ -1274,10 +1274,10 @@ impl WeightInfo for () { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill() -> Weight { // Proof Size summary in bytes: - // Measured: `2430` + // Measured: `2429` // Estimated: `8877` - // Minimum execution time: 95_934_000 picoseconds. - Weight::from_parts(97_624_000, 8877) + // Minimum execution time: 94_864_000 picoseconds. + Weight::from_parts(97_414_000, 8877) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(9_u64)) } @@ -1291,8 +1291,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `935` // Estimated: `4556` - // Minimum execution time: 19_872_000 picoseconds. - Weight::from_parts(20_614_000, 4556) + // Minimum execution time: 20_223_000 picoseconds. + Weight::from_parts(20_997_000, 4556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1306,8 +1306,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1002` // Estimated: `4556` - // Minimum execution time: 23_905_000 picoseconds. - Weight::from_parts(24_901_000, 4556) + // Minimum execution time: 24_225_000 picoseconds. + Weight::from_parts(24_960_000, 4556) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1319,8 +1319,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `935` // Estimated: `8122` - // Minimum execution time: 23_846_000 picoseconds. - Weight::from_parts(24_732_000, 8122) + // Minimum execution time: 24_007_000 picoseconds. + Weight::from_parts(24_897_000, 8122) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1330,8 +1330,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_438_000 picoseconds. - Weight::from_parts(2_597_000, 0) + // Minimum execution time: 2_497_000 picoseconds. + Weight::from_parts(2_626_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1340,8 +1340,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_219_000 picoseconds. - Weight::from_parts(8_685_000, 0) + // Minimum execution time: 7_896_000 picoseconds. + Weight::from_parts(8_402_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1350,8 +1350,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_212_000 picoseconds. - Weight::from_parts(8_703_000, 0) + // Minimum execution time: 8_122_000 picoseconds. + Weight::from_parts(8_432_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1360,8 +1360,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_193_000 picoseconds. - Weight::from_parts(8_622_000, 0) + // Minimum execution time: 8_027_000 picoseconds. + Weight::from_parts(8_393_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::Invulnerables` (r:0 w:1) @@ -1371,10 +1371,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_673_000 picoseconds. - Weight::from_parts(3_260_136, 0) + // Minimum execution time: 2_616_000 picoseconds. + Weight::from_parts(3_281_227, 0) // Standard Error: 32 - .saturating_add(Weight::from_parts(10_059, 0).saturating_mul(v.into())) + .saturating_add(Weight::from_parts(10_003, 0).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::Ledger` (r:11800 w:11800) @@ -1388,10 +1388,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1779 + i * (229 ±0)` // Estimated: `990 + i * (7132 ±0)` - // Minimum execution time: 5_081_000 picoseconds. - Weight::from_parts(5_272_000, 990) - // Standard Error: 106_956 - .saturating_add(Weight::from_parts(30_736_628, 0).saturating_mul(i.into())) + // Minimum execution time: 4_905_000 picoseconds. + Weight::from_parts(5_156_000, 990) + // Standard Error: 83_880 + .saturating_add(Weight::from_parts(30_406_155, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(i.into()))) .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 7132).saturating_mul(i.into())) @@ -1431,12 +1431,12 @@ impl WeightInfo for () { /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2843 + s * (4 ±0)` + // Measured: `2842 + s * (4 ±0)` // Estimated: `8877 + s * (4 ±0)` - // Minimum execution time: 131_018_000 picoseconds. - Weight::from_parts(144_246_801, 8877) - // Standard Error: 4_444 - .saturating_add(Weight::from_parts(1_458_960, 0).saturating_mul(s.into())) + // Minimum execution time: 130_799_000 picoseconds. + Weight::from_parts(142_508_520, 8877) + // Standard Error: 4_715 + .saturating_add(Weight::from_parts(1_440_259, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(17_u64)) .saturating_add(RocksDbWeight::get().writes(16_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -1449,10 +1449,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `66705` // Estimated: `70170` - // Minimum execution time: 96_853_000 picoseconds. - Weight::from_parts(892_001_373, 70170) - // Standard Error: 57_528 - .saturating_add(Weight::from_parts(4_872_094, 0).saturating_mul(s.into())) + // Minimum execution time: 107_433_000 picoseconds. + Weight::from_parts(1_164_974_155, 70170) + // Standard Error: 76_453 + .saturating_add(Weight::from_parts(6_498_608, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1492,7 +1492,7 @@ impl WeightInfo for () { /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) /// Storage: `TargetList::ListNodes` (r:1 w:1) /// Proof: `TargetList::ListNodes` (`max_values`: None, `max_size`: Some(170), added: 2645, mode: `MaxEncodedLen`) - /// Storage: `TargetList::ListBags` (r:8 w:8) + /// Storage: `TargetList::ListBags` (r:13 w:13) /// Proof: `TargetList::ListBags` (`max_values`: None, `max_size`: Some(90), added: 2565, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:257 w:257) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) @@ -1501,15 +1501,15 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `34118 + n * (625 ±0)` - // Estimated: `31763 + n * (3774 ±0)` - // Minimum execution time: 248_954_000 picoseconds. - Weight::from_parts(1_054_655_035, 31763) - // Standard Error: 540_385 - .saturating_add(Weight::from_parts(86_501_516, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(29_u64)) + // Measured: `34145 + n * (625 ±0)` + // Estimated: `31768 + n * (3774 ±3)` + // Minimum execution time: 256_919_000 picoseconds. + Weight::from_parts(1_131_187_427, 31768) + // Standard Error: 504_022 + .saturating_add(Weight::from_parts(84_568_885, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(34_u64)) .saturating_add(RocksDbWeight::get().reads((10_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(16_u64)) + .saturating_add(RocksDbWeight::get().writes(21_u64)) .saturating_add(RocksDbWeight::get().writes((4_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into())) } @@ -1538,13 +1538,15 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2743 + l * (7 ±0)` // Estimated: `11506` - // Minimum execution time: 130_367_000 picoseconds. - Weight::from_parts(134_369_938, 11506) - // Standard Error: 6_686 - .saturating_add(Weight::from_parts(185_942, 0).saturating_mul(l.into())) + // Minimum execution time: 129_295_000 picoseconds. + Weight::from_parts(134_342_502, 11506) + // Standard Error: 6_141 + .saturating_add(Weight::from_parts(203_335, 0).saturating_mul(l.into())) .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(8_u64)) } + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) @@ -1565,8 +1567,6 @@ impl WeightInfo for () { /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::CounterForListNodes` (r:1 w:1) /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::VirtualStakers` (r:1 w:1) - /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -1578,12 +1578,12 @@ impl WeightInfo for () { /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2843 + s * (4 ±0)` + // Measured: `2842 + s * (4 ±0)` // Estimated: `8877 + s * (4 ±0)` - // Minimum execution time: 142_165_000 picoseconds. - Weight::from_parts(150_545_357, 8877) - // Standard Error: 4_445 - .saturating_add(Weight::from_parts(1_439_599, 0).saturating_mul(s.into())) + // Minimum execution time: 142_841_000 picoseconds. + Weight::from_parts(151_373_760, 8877) + // Standard Error: 4_909 + .saturating_add(Weight::from_parts(1_432_397, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(16_u64)) .saturating_add(RocksDbWeight::get().writes(15_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -1633,12 +1633,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0 + n * (720 ±0) + v * (3776 ±0)` // Estimated: `516555 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 957_678_000 picoseconds. - Weight::from_parts(964_225_000, 516555) - // Standard Error: 2_413_573 - .saturating_add(Weight::from_parts(79_822_457, 0).saturating_mul(v.into())) - // Standard Error: 240_499 - .saturating_add(Weight::from_parts(20_562_885, 0).saturating_mul(n.into())) + // Minimum execution time: 963_499_000 picoseconds. + Weight::from_parts(966_544_000, 516555) + // Standard Error: 2_158_092 + .saturating_add(Weight::from_parts(72_956_282, 0).saturating_mul(v.into())) + // Standard Error: 215_042 + .saturating_add(Weight::from_parts(19_482_545, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(410_u64)) .saturating_add(RocksDbWeight::get().reads((6_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -1669,12 +1669,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `3208 + n * (911 ±0) + v * (395 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 37_903_494_000 picoseconds. - Weight::from_parts(38_991_701_000, 512390) - // Standard Error: 406_095 - .saturating_add(Weight::from_parts(8_454_350, 0).saturating_mul(v.into())) - // Standard Error: 406_095 - .saturating_add(Weight::from_parts(2_716_681, 0).saturating_mul(n.into())) + // Minimum execution time: 36_355_465_000 picoseconds. + Weight::from_parts(36_660_578_000, 512390) + // Standard Error: 422_770 + .saturating_add(Weight::from_parts(3_407_216, 0).saturating_mul(v.into())) + // Standard Error: 422_770 + .saturating_add(Weight::from_parts(6_173_669, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(201_u64)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -1697,12 +1697,12 @@ impl WeightInfo for () { /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `58454 + v * (323 ±0)` + // Measured: `58129 + v * (323 ±0)` // Estimated: `516555 + v * (3033 ±0)` - // Minimum execution time: 9_230_123_000 picoseconds. - Weight::from_parts(1_671_253_694, 516555) - // Standard Error: 178_631 - .saturating_add(Weight::from_parts(17_181_912, 0).saturating_mul(v.into())) + // Minimum execution time: 8_890_124_000 picoseconds. + Weight::from_parts(9_016_030_000, 516555) + // Standard Error: 111_852 + .saturating_add(Weight::from_parts(7_039_233, 0).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().reads(206_u64)) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 3033).saturating_mul(v.into())) @@ -1725,8 +1725,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_848_000 picoseconds. - Weight::from_parts(6_161_000, 0) + // Minimum execution time: 5_627_000 picoseconds. + Weight::from_parts(6_134_000, 0) .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// Storage: `Staking::MinCommission` (r:0 w:1) @@ -1747,8 +1747,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_778_000 picoseconds. - Weight::from_parts(5_052_000, 0) + // Minimum execution time: 4_901_000 picoseconds. + Weight::from_parts(5_209_000, 0) .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// Storage: `Staking::Bonded` (r:1 w:0) @@ -1777,10 +1777,10 @@ impl WeightInfo for () { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill_other() -> Weight { // Proof Size summary in bytes: - // Measured: `2545` + // Measured: `2544` // Estimated: `8877` - // Minimum execution time: 109_628_000 picoseconds. - Weight::from_parts(111_707_000, 8877) + // Minimum execution time: 111_325_000 picoseconds. + Weight::from_parts(114_102_000, 8877) .saturating_add(RocksDbWeight::get().reads(15_u64)) .saturating_add(RocksDbWeight::get().writes(9_u64)) } @@ -1792,8 +1792,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `724` // Estimated: `3510` - // Minimum execution time: 12_843_000 picoseconds. - Weight::from_parts(13_310_000, 3510) + // Minimum execution time: 13_264_000 picoseconds. + Weight::from_parts(13_696_000, 3510) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1803,10 +1803,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_051_000 picoseconds. - Weight::from_parts(3_169_000, 0) + // Minimum execution time: 3_052_000 picoseconds. + Weight::from_parts(3_242_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -1815,8 +1817,6 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Staking::VirtualStakers` (r:1 w:0) - /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) @@ -1827,14 +1827,14 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1165` // Estimated: `4764` - // Minimum execution time: 56_877_000 picoseconds. - Weight::from_parts(58_435_000, 4764) + // Minimum execution time: 61_779_000 picoseconds. + Weight::from_parts(62_484_000, 4764) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Staking::Bonded` (r:3 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `TargetList::ListNodes` (r:1 w:1) + /// Storage: `TargetList::ListNodes` (r:2 w:2) /// Proof: `TargetList::ListNodes` (`max_values`: None, `max_size`: Some(170), added: 2645, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:2 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) @@ -1852,12 +1852,12 @@ impl WeightInfo for () { /// Proof: `TargetList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn drop_dangling_nomination() -> Weight { // Proof Size summary in bytes: - // Measured: `1863` + // Measured: `1973` // Estimated: `8631` - // Minimum execution time: 93_338_000 picoseconds. - Weight::from_parts(94_432_000, 8631) - .saturating_add(RocksDbWeight::get().reads(13_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + // Minimum execution time: 101_638_000 picoseconds. + Weight::from_parts(103_488_000, 8631) + .saturating_add(RocksDbWeight::get().reads(14_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Staking::Nominators` (r:6 w:0) /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) @@ -1865,19 +1865,19 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:21 w:0) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `TargetList::ListNodes` (r:78 w:78) + /// Storage: `TargetList::ListNodes` (r:180 w:180) /// Proof: `TargetList::ListNodes` (`max_values`: None, `max_size`: Some(170), added: 2645, mode: `MaxEncodedLen`) - /// Storage: `TargetList::ListBags` (r:4 w:4) + /// Storage: `TargetList::ListBags` (r:2 w:2) /// Proof: `TargetList::ListBags` (`max_values`: None, `max_size`: Some(90), added: 2565, mode: `MaxEncodedLen`) /// Storage: `TargetList::CounterForListNodes` (r:1 w:1) /// Proof: `TargetList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn v13_mmb_step() -> Weight { // Proof Size summary in bytes: - // Measured: `47878` - // Estimated: `207300` - // Minimum execution time: 2_239_356_000 picoseconds. - Weight::from_parts(2_284_677_000, 207300) - .saturating_add(RocksDbWeight::get().reads(131_u64)) - .saturating_add(RocksDbWeight::get().writes(83_u64)) + // Measured: `70141` + // Estimated: `477090` + // Minimum execution time: 2_703_732_000 picoseconds. + Weight::from_parts(2_779_206_000, 477090) + .saturating_add(RocksDbWeight::get().reads(231_u64)) + .saturating_add(RocksDbWeight::get().writes(183_u64)) } } diff --git a/substrate/frame/staking/stake-tracker/src/lib.rs b/substrate/frame/staking/stake-tracker/src/lib.rs index b8e6ff59a455..c24f8aa289f5 100644 --- a/substrate/frame/staking/stake-tracker/src/lib.rs +++ b/substrate/frame/staking/stake-tracker/src/lib.rs @@ -23,28 +23,32 @@ //! ## Overview //! //! The stake-tracker pallet listens to staking events through implementing the [`OnStakingUpdate`] -//! trait. Based on those events, maintais a *strictly* sorted list of target lists based on their -//! approval voting power. +//! trait. Based on the emitted events, the goal of this pallet is to maintain a **strictly** +//! sorted list of targets by approval voting. This pallet may also update a voter list, based on +//! the configurations. //! -//! For the voter list, the [`crate::SortingMode`] defines the type of sortition of the list, +//! For the voter list, the [`crate::VoterUpdateMode`] defines the type of sortition of the list, //! namely: //! -//! - [`crate::SortingMode::Lazy`]: will skip the score update in the voter list. -//! - [`crate::SortingMode::Strict`]: will ensure that the score updates are kept sorted +//! - [`crate::VoterUpdateMode::Lazy`]: will skip the score update in the voter list. +//! - [`crate::VoterUpdateMode::Strict`]: will ensure that the score updates are kept sorted //! for the corresponding list. In this case, the [`Config::VoterList`] is *strictly* //! sorted* by [`SortedListProvider::Score`] (note: from the time the sorting mode is strict). //! +//! Note that insertions and removals of voter nodes will be executed regardless of the sorting +//! mode. +//! //! ## Goals //! //! Note: these goals are assuming the both target list and sorted lists have -//! [`crate::SortingMode::Strict`] set. +//! [`crate::VoterUpdateMode::Strict`] set. //! //! The [`OnStakingUpdate`] implementation (in strict mode) aims to achieve the following goals: //! //! * The [`Config::TargetList`] keeps a sorted list of validators, *strictly* sorted by approvals //! (which include self-vote and nominations' stake). //! * The [`Config::VoterList`] keeps a list of voters, *stricly* sorted by bonded stake if it has -//! [`crate::SortingMode::strict`] mode enabled, otherwise the list is kept lazily sorted. +//! [`crate::VoterUpdateMode::Strict`] mode enabled, otherwise the list is kept lazily sorted. //! * The [`Config::TargetList`] sorting must be *always* kept up to date, even in the event of new //! nomination updates, nominator/validator slashes and rewards. This pallet *must* ensure that the //! scores of the targets and voters are always up to date and thus, that the targets and voters in @@ -59,7 +63,7 @@ //! ## Staker status and list invariants //! //! Note: these goals are assuming the both target list and sorted lists have -//! [`crate::SortingMode::Strict`] set. +//! [`crate::VoterUpdateMode::Strict`] set. //! //! * A [`sp_staking::StakerStatus::Nominator`] is part of the voter list and its self-stake is the //! voter list's score. In addition, if the `VoterList` is in strict mode, the voters' scores are up @@ -75,7 +79,7 @@ //! be removed onced all the voters stop nominating the unbonded account (i.e. the target's score //! drops to 0). //! -//! For further details on the target list invariantes, refer to [`Self`::do_try_state_approvals`] +//! For further details on the target list invariante, refer to [`Self`::do_try_state_approvals`] //! and [`Self::do_try_state_target_sorting`]. //! //! ## Event emitter ordering and staking ledger state updates @@ -94,7 +98,7 @@ use frame_support::{ pallet_prelude::*, traits::{fungible::Inspect as FnInspect, Defensive, DefensiveSaturating}, }; -use sp_runtime::traits::Zero; +use sp_runtime::traits::{Saturating, Zero}; use sp_staking::{ currency_to_vote::CurrencyToVote, OnStakingUpdate, Stake, StakerStatus, StakingInterface, }; @@ -105,19 +109,6 @@ pub(crate) mod mock; #[cfg(test)] mod tests; -pub(crate) const LOG_TARGET: &str = "runtime::stake-tracker"; - -// syntactic sugar for logging. -#[macro_export] -macro_rules! log { - ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { - log::$level!( - target: $crate::LOG_TARGET, - concat!("[{:?}] 📚 ", $patter), >::block_number() $(, $values)* - ) - }; -} - /// The balance type of this pallet. pub type BalanceOf = <::Staking as StakingInterface>::Balance; /// The account ID of this pallet. @@ -125,42 +116,37 @@ pub type AccountIdOf = ::AccountId; /// Represents a stake imbalance to be applied to a staker's score. #[derive(Copy, Clone, Debug)] -pub enum StakeImbalance { - /// Represents the reduction of stake by `Balance`. - Negative(Balance), - /// Represents the increase of stake by `Balance`. - Positive(Balance), +pub enum StakeImbalance { + /// Represents the reduction of stake by `Score`. + Negative(Score), + /// Represents the increase of stake by `Score`. + Positive(Score), } -impl StakeImbalance { - fn from(prev_balance: Balance, new_balance: Balance) -> Self { - if prev_balance > new_balance { - StakeImbalance::Negative(prev_balance.defensive_saturating_sub(new_balance)) +impl StakeImbalance { + /// Constructor for a stake imbalance instance based on the previous and next score. + fn from(prev: Score, new: Score) -> Self { + if prev > new { + StakeImbalance::Negative(prev.defensive_saturating_sub(new)) } else { - StakeImbalance::Positive(new_balance.defensive_saturating_sub(prev_balance)) + StakeImbalance::Positive(new.defensive_saturating_sub(prev)) } } } /// Defines the sorting mode of sorted list providers. -/// -/// Strict: all score update events will be autimatically reflected in the sorted list. -/// Lazy: no score update events will be automatically reflected in the sorted list. -#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub enum SortingMode { +#[derive(Copy, Clone, Debug)] +pub enum VoterUpdateMode { + /// All score update events will be automatically reflected in the sorted list. Strict, + /// Score update events are *not* be automatically reflected in the sorted list. Howeber, node + /// insertion and removals are reflected in the list. Lazy, } -impl Default for SortingMode { - fn default() -> Self { - Self::Strict - } -} - -impl SortingMode { - fn is_lazy_mode(&self) -> bool { - matches!(self, Self::Lazy) +impl VoterUpdateMode { + fn is_strict_mode(&self) -> bool { + matches!(self, Self::Strict) } } @@ -182,27 +168,20 @@ pub mod pallet { /// The stake balance. type Currency: FnInspect>; - /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// The staking interface. type Staking: StakingInterface; - /// Something that provides an *always* sorted list of voters. + /// A sorted list provider for staking voters that is kept up to date by this pallet. The + /// sorting by score depends on the sorting mode set by [`Self::VoterUpdateMode`]. type VoterList: SortedListProvider; - /// Something that provides an *always* sorted list of targets by their approval stake. - type TargetList: SortedListProvider< - Self::AccountId, - Score = ::Balance, - >; - } - - #[pallet::storage] - pub type VoterListMode = StorageValue<_, SortingMode, ValueQuery>; + /// A sorted list provider for staking targets that is ketp *always* sorted by the target's + /// stake approvals. + type TargetList: SortedListProvider; - #[pallet::event] - pub enum Event {} + /// The voter list update mode. + type VoterUpdateMode: Get; + } #[pallet::hooks] impl Hooks> for Pallet { @@ -213,48 +192,65 @@ pub mod pallet { } impl Pallet { - /// Returns the balance of a staker based on its current *active* stake, as returned by - /// the staking interface. - pub(crate) fn active_vote_of(who: &T::AccountId) -> BalanceOf { - T::Staking::stake(who).map(|s| s.active).defensive_unwrap_or_default() - } + /// Updates the stake of a voter. + /// + /// It must ensure that there are no duplicate nominations to prevent over-counting the + /// stake approval. + pub(crate) fn do_stake_update_voter( + who: &T::AccountId, + prev_stake: Option>>, + stake: Stake>, + nominations: Vec, + ) { + let voter_weight = Self::to_vote(stake.active); - /// Converts a balance into the staker's vote weight. - pub(crate) fn weight_of(balance: BalanceOf) -> VoteWeight { - ::CurrencyToVote::to_vote( - balance, - T::Currency::total_issuance(), - ) - } + // if voter list is in strict sorting mode, update the voter score too. + if T::VoterUpdateMode::get().is_strict_mode() { + let _ = T::VoterList::on_update(who, voter_weight).defensive_proof( + "staker should exist in VoterList, as per the contract \ + with staking.", + ); + } - /// Fetches and converts a voter's weight into the [`ExtendedBalance`] type for safe - /// computation. - pub(crate) fn to_vote_extended(balance: BalanceOf) -> ExtendedBalance { - ::CurrencyToVote::to_vote( - balance, - T::Currency::total_issuance(), - ) - .into() - } + let stake_imbalance = StakeImbalance::from( + prev_stake.map_or(Default::default(), |s| Self::to_vote(s.active).into()), + voter_weight.into(), + ); - /// Converts an [`sp_npos_elections::ExtendedBalance`] back to the staking interface's - /// balance. - pub(crate) fn to_currency( - extended: ExtendedBalance, - ) -> ::Balance { - ::CurrencyToVote::to_currency( - extended, - T::Currency::total_issuance(), - ) + // note: this dedup can be removed once the staking pallet ensures no duplicate + // nominations are allowed . + // TODO: replace the dedup by a debug_assert once #4419 is resolved. + let nominations = Self::ensure_dedup(nominations); + + // updates vote weight of nominated targets accordingly. Note: this will + // update the score of up to `T::MaxNominations` validators. + for target in nominations.into_iter() { + Self::update_target_score(&target, stake_imbalance); + } } - /// Returns whether a target should be removed from the target list. - /// - /// A target should be removed from the target list at any point IFF: - /// * it's approvals are 0 AND - /// * it's state is dangling (ledger unbonded). - pub(crate) fn should_remove_target(who: &T::AccountId, score: BalanceOf) -> bool { - score.is_zero() && T::Staking::status(who).is_err() + /// Updates the stake of a target. + pub(crate) fn do_stake_update_target( + who: &T::AccountId, + prev_stake: Option>>, + stake: Stake>, + ) { + let voter_weight = Self::to_vote(stake.active).into(); + let stake_imbalance = StakeImbalance::from( + prev_stake.map_or(Default::default(), |s| Self::to_vote(s.active).into()), + voter_weight, + ); + + Self::update_target_score(who, stake_imbalance); + + // validator is both a target and a voter. update the voter score if the voter list + // is in strict mode. + if T::VoterUpdateMode::get().is_strict_mode() { + let _ = T::VoterList::on_update(who, Self::to_vote(stake.active)).defensive_proof( + "the staker should exist in VoterList, as per the \ + contract with staking.", + ); + } } /// Updates a target's score by increasing/decreasing an imbalance of the current score in @@ -263,72 +259,72 @@ pub mod pallet { who: &T::AccountId, imbalance: StakeImbalance, ) { - // ensure that the target list node exists if it does not yet and perform a few - // defensive checks. + // if target list does not contain target, add it and proceed. if !T::TargetList::contains(who) { - match T::Staking::status(who) { - Err(_) | Ok(StakerStatus::Nominator(_)) => { - defensive!("update target score was called on an unbonded ledger or nominator, not expected."); - return - }, - Ok(StakerStatus::Validator) => { - defensive!( - "active validator was not part of the target list, something is wrong." - ); - return - }, - Ok(StakerStatus::Idle) => { - // if stash is idle and not part of the target list yet, initialize it and - // proceed. - T::TargetList::on_insert(who.clone(), Zero::zero()) - .expect("staker does not exist in the list as per check above; qed."); - }, - } + T::TargetList::on_insert(who.clone(), Zero::zero()) + .expect("staker does not exist in the list as per check above; qed."); } // update target score. - let removed = match imbalance { + match imbalance { StakeImbalance::Positive(imbalance) => { - let _ = T::TargetList::on_increase(who, Self::to_currency(imbalance)) - .defensive_proof( - "staker should exist in the list, otherwise returned earlier.", - ); - false + let _ = T::TargetList::on_increase(who, imbalance).defensive_proof( + "staker should exist in the list, otherwise returned earlier.", + ); }, StakeImbalance::Negative(imbalance) => { if let Ok(current_score) = T::TargetList::get_score(who) { - let balance = - Self::to_vote_extended(current_score).saturating_sub(imbalance); + let balance = current_score.saturating_sub(imbalance); // the target is removed from the list IFF score is 0 and the target is - // dangling (i.e. not bonded). - if Self::should_remove_target(who, Self::to_currency(balance)) { + // either dangling (i.e. not bonded) or currently registered as a nominator. + let remove_target = balance.is_zero() && + T::Staking::status(who).map(|s| s.is_nominator()).unwrap_or(true); + + if remove_target { let _ = T::TargetList::on_remove(who).defensive_proof( "staker exists in the list as per the check above; qed.", ); - true } else { // update the target score without removing it. - let _ = T::TargetList::on_update(who, Self::to_currency(balance)) - .defensive_proof( - "staker exists in the list as per the check above; qed.", - ); - false + let _ = T::TargetList::on_update(who, balance).defensive_proof( + "staker exists in the list as per the check above; qed.", + ); } } else { defensive!("unexpected: unable to fetch score from staking interface of an existent staker"); - false } }, }; + } - log!( - debug, - "update_score of {:?} by {:?}. removed target node? {}", - who, - imbalance, - removed - ); + // ------ Helpers + + /// Helper to convert the balance of a staker into its vote weight. + pub(crate) fn to_vote(balance: BalanceOf) -> VoteWeight { + ::CurrencyToVote::to_vote( + balance, + T::Currency::total_issuance(), + ) + } + + /// Helper to fetch te active stake of a staker and convert it to vote weight. + pub(crate) fn vote_of(who: &T::AccountId) -> VoteWeight { + let active = T::Staking::stake(who).map(|s| s.active).defensive_unwrap_or_default(); + Self::to_vote(active) + } + + /// Returns a dedup list of accounts. + /// + /// Note: this dedup can be removed once (and if) the staking pallet ensures no duplicate + /// nominations are allowed . + /// + /// TODO: replace this helper method by a debug_assert if #4419 ever prevents the nomination + /// of duplicated target. + pub(crate) fn ensure_dedup(mut v: Vec) -> Vec { + use sp_std::collections::btree_set::BTreeSet; + + v.drain(..).collect::>().into_iter().collect::>() } } } @@ -386,35 +382,50 @@ impl Pallet { // build map of approvals stakes from the `VoterList` POV. for voter in T::VoterList::iter() { if let Some(nominations) = ::nominations(&voter) { - let score = >>::get_score(&voter) - .map_err(|_| "nominator score must exist in voter bags list")?; - // sanity check. let active_stake = T::Staking::stake(&voter) - .map(|s| Self::weight_of(s.active)) + .map(|s| Self::to_vote(s.active)) .expect("active voter has bonded stake; qed."); - // if the voter list is in lazy mode, we don't expect the stake of the voter to - // match the score in the list at all times. - if !VoterListMode::::get().is_lazy_mode() { + // if the voter list is in strict mode, we expect the stake of the voter to match + // the score in the list at all times. The approvals calculation also depends on + // the sorting of the voter list: + // * if the voter list is strictly sorted, use the nominator's scores to calculate + // the approvals. + // * if the voter list is lazily sorted, use the active stake of the nominator to + // calculat the approvals. + let stake = if T::VoterUpdateMode::get().is_strict_mode() { + // voter list is strictly sorted, use the voter list score to calculate the + // target's approvals. + let score = + >>::get_score(&voter) + .map_err(|_| "nominator score must exist in voter bags list")?; + frame_support::ensure!( active_stake == score, "voter score must be the same as its active stake" ); - } + + score + } else { + active_stake + }; + + // update the approvals map with the voter's active stake. + // note: must remove the dedup nominations, which is also done by the + // stake-tracker. + let nominations = Self::ensure_dedup(nominations); for nomination in nominations { - if let Some(stake) = approvals_map.get_mut(&nomination) { - *stake += score as sp_npos_elections::ExtendedBalance; - } else { - approvals_map.insert(nomination, score.into()); - } + *approvals_map.entry(nomination).or_default() += + stake as sp_npos_elections::ExtendedBalance; } } else { // if it is in the voter list but it's not a nominator, it should be a validator // and part of the target list. frame_support::ensure!( - T::Staking::status(&voter) == Ok(StakerStatus::Validator), + T::Staking::status(&voter) == Ok(StakerStatus::Validator) && + T::TargetList::contains(&voter), "wrong state of voter" ); frame_support::ensure!( @@ -463,10 +474,11 @@ impl Pallet { "bonded and active validator should also be part of the voter list" ); // return self-stake (ie. active bonded). - T::Staking::stake(&target).map(|s| Self::weight_of(s.active)).ok() + T::Staking::stake(&target).map(|s| Self::to_vote(s.active)).ok() }, Ok(StakerStatus::Nominator(_)) => { - panic!("staker with nominator status should not be part of the target list"); + // an idle/dangling target may become a nominator. + None }, }; @@ -481,21 +493,18 @@ impl Pallet { } } - // if the target list is in lazy mode, we don't expect the stake approvals of the target - // to be correct at all times. // compare calculated approvals per target with target list state. for (target, calculated_stake) in approvals_map.iter() { let stake_in_list = T::TargetList::get_score(target).expect("target must exist; qed."); - let stake_in_list = Self::to_vote_extended(stake_in_list); if *calculated_stake != stake_in_list { - log!( - error, - "try-runtime: score of {:?} in `TargetList` list: {:?}, calculated sum of all stake: {:?}", - target, - stake_in_list, - calculated_stake, - ); + log::error!( + target: "runtime::stake-tracker", + "try-runtime: score of {:?} in `TargetList` list: {:?}, calculated sum of all stake: {:?}", + target, + stake_in_list, + calculated_stake, + ); return Err("target score in the target list is different than the expected".into()) } @@ -541,7 +550,7 @@ impl Pallet { pub fn do_try_state_voter_sorting() -> Result<(), sp_runtime::TryRuntimeError> { // if the voter list is in lazy mode, we don't expect the nodes to be sorted at all times. // skip checks. - if !VoterListMode::::get().is_lazy_mode() { + if T::VoterUpdateMode::get().is_strict_mode() { return Ok(()) } @@ -560,53 +569,18 @@ impl OnStakingUpdate> for Pallet { /// When a nominator's stake is updated, all the nominated targets must be updated /// accordingly. /// - /// Note: it is assumed that `who`'s staking ledger state is updated *before* this method is - /// called. + /// The score of the node associated with `who` in the *VoterList* will be updated if the + /// the mode is [`VoterUpdateMode::Strict`]. The approvals of the nominated targets (by `who`) + /// are always updated. fn on_stake_update( who: &T::AccountId, prev_stake: Option>>, stake: Stake>, ) { match T::Staking::status(who) { - Ok(StakerStatus::Nominator(nominations)) => { - // if voters are in lazy mode, return without updating the stake. - if VoterListMode::::get().is_lazy_mode() { - return - } - - let voter_weight = Self::weight_of(stake.active); - - let _ = T::VoterList::on_update(who, voter_weight).defensive_proof( - "staker should exist in VoterList, as per the contract \ - with staking.", - ); - - let stake_imbalance = StakeImbalance::from( - prev_stake.map_or(Default::default(), |s| Self::to_vote_extended(s.active)), - voter_weight.into(), - ); - - // updates vote weight of nominated targets accordingly. Note: this will - // update the score of up to `T::MaxNominations` validators. - for target in nominations.into_iter() { - Self::update_target_score(&target, stake_imbalance); - } - }, - Ok(StakerStatus::Validator) => { - let voter_weight = Self::weight_of(stake.active); - let stake_imbalance = StakeImbalance::from( - prev_stake.map_or(Default::default(), |s| Self::to_vote_extended(s.active)), - voter_weight.into(), - ); - - Self::update_target_score(who, stake_imbalance); - - // validator is both a target and a voter. - let _ = T::VoterList::on_update(who, voter_weight).defensive_proof( - "the staker should exist in VoterList, as per the \ - contract with staking.", - ); - }, + Ok(StakerStatus::Nominator(nominations)) => + Self::do_stake_update_voter(who, prev_stake, stake, nominations), + Ok(StakerStatus::Validator) => Self::do_stake_update_target(who, prev_stake, stake), Ok(StakerStatus::Idle) => (), // nothing to see here. Err(_) => { defensive!( @@ -622,7 +596,7 @@ impl OnStakingUpdate> for Pallet { /// Note: it is assumed that `who`'s ledger staking state is updated *before* calling this /// method. fn on_validator_add(who: &T::AccountId, self_stake: Option>>) { - let self_stake = self_stake.unwrap_or_default().active; + let self_stake = Self::to_vote(self_stake.unwrap_or_default().active).into(); match T::TargetList::on_insert(who.clone(), self_stake) { Ok(_) => (), @@ -634,13 +608,10 @@ impl OnStakingUpdate> for Pallet { T::Staking::status(who).is_err() ); - let self_stake = Self::to_vote_extended(self_stake); Self::update_target_score(who, StakeImbalance::Positive(self_stake)); }, } - log!(debug, "on_validator_add: {:?}. role: {:?}", who, T::Staking::status(who),); - // a validator is also a nominator. Self::on_nominator_add(who, vec![]) } @@ -650,21 +621,18 @@ impl OnStakingUpdate> for Pallet { /// While idling, the target node is not removed from the target list but its score is /// updated. fn on_validator_idle(who: &T::AccountId) { - let self_stake = Self::weight_of(Self::active_vote_of(who)); + let self_stake = Self::vote_of(who); Self::update_target_score(who, StakeImbalance::Negative(self_stake.into())); // validator is a nominator too. Self::on_nominator_idle(who, vec![]); - - log!(debug, "on_validator_idle: {:?}, decreased self-stake {}", who, self_stake); } - /// A validator has been set as inactive/removed from the staking POV. The target node is - /// removed from the target list IFF its score is 0. Otherwise, its score should be kept up to - /// date as if the validator was active. + /// A validator has been set as inactive/removed from the staking POV. + /// + /// The target node is removed from the target list IFF its score is 0. Otherwise, its score + /// should be kept up to date as if the validator was active. fn on_validator_remove(who: &T::AccountId) { - log!(debug, "on_validator_remove: {:?} with status {:?}", who, T::Staking::status(who)); - // validator must be idle before removing completely. Perform some sanity checks too. match T::Staking::status(who) { Ok(StakerStatus::Idle) => (), // proceed @@ -692,16 +660,24 @@ impl OnStakingUpdate> for Pallet { }; } + /// A nominator has been added to the system. + /// + /// Even in lazy mode, inserting voter list nodes on new nominator must be done. + /// /// Note: it is assumed that `who`'s ledger staking state is updated *before* this method is /// called. fn on_nominator_add(who: &T::AccountId, nominations: Vec>) { - let nominator_vote = Self::weight_of(Self::active_vote_of(who)); + let nominator_vote = Self::vote_of(who); + let nominations = Self::ensure_dedup(nominations); + // the new voter node will be added even if the voter is in lazy mode. In lazy mode, we + // ensure that the nodes exist in the voter list, even though they may not have the updated + // score at all times. let _ = T::VoterList::on_insert(who.clone(), nominator_vote).defensive_proof( "the nominator must not exist in the list as per the contract with staking.", ); - // If who is a nominator, update the vote weight of the nominations if they exist. Note: + // if `who` is a nominator, update the vote weight of the nominations if they exist. Note: // this will update the score of up to `T::MaxNominations` validators. match T::Staking::status(who).defensive() { Ok(StakerStatus::Nominator(_)) => @@ -710,8 +686,6 @@ impl OnStakingUpdate> for Pallet { }, Ok(StakerStatus::Idle) | Ok(StakerStatus::Validator) | Err(_) => (), // nada. }; - - log!(debug, "on_nominator_add: {:?}. role: {:?}", who, T::Staking::status(who),); } /// A nominator has been idle. From the `T::VotertList` PoV, chilling a nominator is the same as @@ -726,18 +700,13 @@ impl OnStakingUpdate> for Pallet { /// Fired when someone removes their intention to nominate and is completely removed from /// the staking state. /// + /// Even in lazy mode, removing voter list nodes on nominator remove must be done. + /// /// Note: the number of nodes that are updated is bounded by the maximum number of /// nominators, which is defined in the staking pallet. fn on_nominator_remove(who: &T::AccountId, nominations: Vec) { - let nominator_vote = Self::weight_of(Self::active_vote_of(who)); - - log!( - debug, - "remove nominations from {:?} with {:?} weight. impacting {:?}.", - who, - nominator_vote, - nominations, - ); + let nominator_vote = Self::vote_of(who); + let nominations = Self::ensure_dedup(nominations); // updates the nominated target's score. for t in nominations.iter() { @@ -760,21 +729,18 @@ impl OnStakingUpdate> for Pallet { prev_nominations: Vec, nominations: Vec, ) { - let nominator_vote = Self::weight_of(Self::active_vote_of(who)); + let nominator_vote = Self::vote_of(who); - log!( - debug, - "on_nominator_update: {:?}, with {:?}. previous nominations: {:?} -> new nominations {:?}", - who, nominator_vote, prev_nominations, nominations, - ); + let nominations = Self::ensure_dedup(nominations); + let prev_nominations = Self::ensure_dedup(prev_nominations); - // new nominations + // new nominations. for target in nominations.iter() { if !prev_nominations.contains(target) { Self::update_target_score(target, StakeImbalance::Positive(nominator_vote.into())); } } - // removed nominations + // removed nominations. for target in prev_nominations.iter() { if !nominations.contains(target) { Self::update_target_score(target, StakeImbalance::Negative(nominator_vote.into())); @@ -782,6 +748,36 @@ impl OnStakingUpdate> for Pallet { } } + /// This is called when a staker is slashed. + /// + /// From the stake-tracker POV, no direct changes should be made to the target or voter list in + /// this event handler, since the stake updates from a slash will be indirectly performed + /// through the call to `on_stake_update`. + /// + /// However, if a slash of a nominator results on its active stake becoming 0, the stake + /// tracker *requests* the staking interface to chill the nominator in order to ensure that + /// their nominations are dropped. This way, we ensure that in the event of a validator and all + /// its nominators are 100% slashed, the target can be reaped/killed without leaving + /// nominations behind. + fn on_slash( + stash: &T::AccountId, + _slashed_active: BalanceOf, + _slashed_unlocking: &BTreeMap>, + slashed_total: BalanceOf, + ) { + let active_after_slash = T::Staking::stake(stash) + .defensive_unwrap_or_default() + .active + .saturating_sub(slashed_total); + + match (active_after_slash.is_zero(), T::Staking::status(stash)) { + (true, Ok(StakerStatus::Nominator(_))) => { + let _ = T::Staking::chill(stash).defensive(); + }, + _ => (), // do nothing. + }; + } + // no-op events. /// The score of the staker `who` is updated through the `on_stake_update` calls following the @@ -791,14 +787,4 @@ impl OnStakingUpdate> for Pallet { /// The score of the staker `who` is updated through the `on_stake_update` calls following the /// withdraw. fn on_withdraw(_who: &T::AccountId, _amount: BalanceOf) {} - - /// The score of the staker `who` is updated through the `on_stake_update` calls following the - /// slash. - fn on_slash( - _stash: &T::AccountId, - _slashed_active: BalanceOf, - _slashed_unlocking: &BTreeMap>, - _slashed_total: BalanceOf, - ) { - } } diff --git a/substrate/frame/staking/stake-tracker/src/mock.rs b/substrate/frame/staking/stake-tracker/src/mock.rs index 3953a1a4d8e5..abc8b999e453 100644 --- a/substrate/frame/staking/stake-tracker/src/mock.rs +++ b/substrate/frame/staking/stake-tracker/src/mock.rs @@ -69,11 +69,16 @@ impl pallet_balances::Config for Test { type MaxFreezes = (); } -const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = +const VOTER_THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = [100, 200, 300, 400, 500, 600, 700, 800, 900]; +const TARGET_THRESHOLDS: [u128; 9] = [100, 200, 300, 400, 500, 600, 700, 800, 900]; + parameter_types! { - pub static BagThresholds: &'static [VoteWeight] = &THRESHOLDS; + pub static VoterBagThresholds: &'static [VoteWeight] = &VOTER_THRESHOLDS; + pub static TargetBagThresholds: &'static [u128] = &TARGET_THRESHOLDS; + + pub static VoterUpdateMode: crate::VoterUpdateMode = crate::VoterUpdateMode::Strict; } type VoterBagsListInstance = pallet_bags_list::Instance1; @@ -81,7 +86,7 @@ impl pallet_bags_list::Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type ScoreProvider = StakingMock; - type BagThresholds = BagThresholds; + type BagThresholds = VoterBagThresholds; type Score = VoteWeight; } @@ -90,16 +95,16 @@ impl pallet_bags_list::Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type ScoreProvider = pallet_bags_list::Pallet; - type BagThresholds = BagThresholds; - type Score = ::Balance; + type BagThresholds = TargetBagThresholds; + type Score = u128; } impl pallet_stake_tracker::Config for Test { type Currency = Balances; - type RuntimeEvent = RuntimeEvent; type Staking = StakingMock; type VoterList = VoterBagsList; type TargetList = TargetBagsList; + type VoterUpdateMode = VoterUpdateMode; } pub struct StakingMock {} @@ -273,10 +278,16 @@ parameter_types! { pub static Bonded: Vec = Default::default(); } -pub(crate) fn get_scores>( -) -> Vec<(AccountId, Balance)> { - let scores: Vec<_> = L::iter().map(|e| (e, L::get_score(&e).unwrap())).collect(); - scores +pub(crate) fn target_scores() -> Vec<(AccountId, u128)> { + TargetBagsList::iter() + .map(|e| (e, TargetBagsList::get_score(&e).unwrap())) + .collect::>() +} + +pub(crate) fn voter_scores() -> Vec<(AccountId, Balance)> { + VoterBagsList::iter() + .map(|e| (e, VoterBagsList::get_score(&e).unwrap())) + .collect::>() } pub(crate) fn populate_lists() { @@ -308,6 +319,8 @@ pub(crate) fn score_of_target(who: AccountId) -> Balance { as ScoreProvider>::score( &who, ) + .try_into() + .unwrap() } pub(crate) fn add_nominator_with_nominations( @@ -463,6 +476,11 @@ impl ExtBuilder { self } + pub fn voter_update_mode(self, mode: crate::VoterUpdateMode) -> Self { + VoterUpdateMode::set(mode); + self + } + #[allow(dead_code)] pub fn try_state(self, enable: bool) -> Self { DisableTryRuntimeChecks::set(!enable); @@ -486,9 +504,8 @@ impl ExtBuilder { } // move past genesis to register events. System::set_block_number(1); - - test() }); + ext.execute_with(test); if !DisableTryRuntimeChecks::get() { ext.execute_with(|| { diff --git a/substrate/frame/staking/stake-tracker/src/tests.rs b/substrate/frame/staking/stake-tracker/src/tests.rs index 9c6221c25b03..23ae00778fea 100644 --- a/substrate/frame/staking/stake-tracker/src/tests.rs +++ b/substrate/frame/staking/stake-tracker/src/tests.rs @@ -17,7 +17,7 @@ #![cfg(test)] -use crate::{mock::*, SortingMode, StakeImbalance, VoterListMode}; +use crate::{mock::*, StakeImbalance}; use frame_election_provider_support::{ScoreProvider, SortedListProvider}; use frame_support::assert_ok; @@ -116,7 +116,7 @@ fn on_stake_update_works() { let nomination_score_after = TargetBagsList::get_score(&nominations[0]).unwrap(); assert_eq!( nomination_score_after, - nomination_score_before - (stake_before.unwrap().active - new_stake.active) + nomination_score_before - (stake_before.unwrap().active - new_stake.active) as u128 ); }); @@ -148,38 +148,44 @@ fn on_stake_update_works() { // the stake imbalance of previous and the new stake, in order to not touch the nomination's // weight in the total target score). let target_score_after = TargetBagsList::get_score(&10).unwrap(); - assert_eq!(target_score_after, target_score_before - stake_imbalance); + assert_eq!(target_score_after, target_score_before - stake_imbalance as u128); }) } #[test] fn on_stake_update_lazy_voters_works() { - ExtBuilder::default().populate_lists().build_and_execute(|| { - VoterListMode::::set(SortingMode::Lazy); + ExtBuilder::default() + .populate_lists() + .voter_update_mode(crate::VoterUpdateMode::Lazy) + .build_and_execute(|| { + assert!(VoterBagsList::contains(&1)); + let stake_before = stake_of(1); - assert!(VoterBagsList::contains(&1)); - let stake_before = stake_of(1); + let nominations = ::nominations(&1).unwrap(); + assert!(nominations.len() == 1); - let nominations = ::nominations(&1).unwrap(); - assert!(nominations.len() == 1); - let nomination_score_before = TargetBagsList::get_score(&nominations[0]).unwrap(); + let target_score_before = TargetBagsList::get_score(&10).unwrap(); - // manually change the stake of the voter. - let new_stake = Stake { total: 10, active: 10 }; - // assert imbalance of the operation is negative. - assert!(stake_before.unwrap().active > new_stake.active); + // manually change the stake of the voter. + let new_stake = Stake { total: 10, active: 10 }; + // assert imbalance of the operation is negative. + assert!(stake_before.unwrap().active > new_stake.active); + let stake_imbalance = stake_before.unwrap().active - new_stake.total; - TestNominators::mutate(|n| { - n.insert(1, (new_stake, nominations.clone())); - }); + TestNominators::mutate(|n| { + n.insert(1, (new_stake, nominations.clone())); + }); - >::on_stake_update(&1, stake_before, new_stake); + >::on_stake_update(&1, stake_before, new_stake); - // score of voter did not update, since the voter list is lazily updated. - assert_eq!(VoterBagsList::get_score(&1).unwrap(), stake_before.unwrap().active); - let nomination_score_after = TargetBagsList::get_score(&nominations[0]).unwrap(); - assert_eq!(nomination_score_after, nomination_score_before,); - }); + // score of voter did not update, since the voter list is lazily updated. + assert_eq!(VoterBagsList::get_score(&1).unwrap(), stake_before.unwrap().active); + + // however, the target's approvals are *always* updated, regardless of the voter's + // sorting mode. + let target_score_after = TargetBagsList::get_score(&10).unwrap(); + assert_eq!(target_score_after, target_score_before - stake_imbalance as u128); + }); } #[test] @@ -224,7 +230,7 @@ fn on_stake_update_sorting_works() { ExtBuilder::default().populate_lists().build_and_execute(|| { // [(10, 100), (11, 100), (1, 100), (2, 100)] - let voter_scores_before = get_scores::(); + let voter_scores_before = voter_scores(); assert_eq!(voter_scores_before, [(10, 100), (11, 100), (1, 100), (2, 100)]); // noop, nothing changes. @@ -234,7 +240,7 @@ fn on_stake_update_sorting_works() { initial_stake, initial_stake.unwrap(), ); - assert_eq!(voter_scores_before, get_scores::()); + assert_eq!(voter_scores_before, voter_scores()); // now let's change the self-vote of 11 and call `on_stake_update` again. let nominations = ::nominations(&11).unwrap(); @@ -384,7 +390,7 @@ fn on_nominator_remove_works() { // now, the score of the nominated by 1 has less `nominator_score` stake than before the // nominator was removed. let nomination_score_after = TargetBagsList::get_score(&nominations[0]).unwrap(); - assert!(nomination_score_after == nomination_score_before - nominator_score); + assert!(nomination_score_after == nomination_score_before - nominator_score as u128); }) } @@ -504,7 +510,7 @@ mod staking_integration { #[test] fn on_remove_stakers_with_nominations_works() { ExtBuilder::default().populate_lists().build_and_execute(|| { - assert_eq!(get_scores::(), vec![(10, 300), (11, 200)]); + assert_eq!(target_scores(), vec![(10, 300), (11, 200)]); assert!(VoterBagsList::contains(&1)); assert_eq!(VoterBagsList::get_score(&1), Ok(100)); @@ -521,52 +527,39 @@ mod staking_integration { #[test] fn on_nominator_update_works() { ExtBuilder::default().populate_lists().build_and_execute(|| { - assert_eq!( - get_scores::(), - vec![(10, 100), (11, 100), (1, 100), (2, 100)] - ); - assert_eq!(get_scores::(), vec![(10, 300), (11, 200)]); + assert_eq!(voter_scores(), vec![(10, 100), (11, 100), (1, 100), (2, 100)]); + assert_eq!(target_scores(), vec![(10, 300), (11, 200)]); add_validator(20, 500); // removes nomination from 10 and adds nomination to new validator, 20. update_nominations_of(2, vec![11, 20]); - assert_eq!( - get_scores::(), - [(20, 500), (10, 100), (11, 100), (1, 100), (2, 100)] - ); + assert_eq!(voter_scores(), [(20, 500), (10, 100), (11, 100), (1, 100), (2, 100)]); // target list has been updated: - assert_eq!(get_scores::(), vec![(20, 600), (11, 200), (10, 200)]); + assert_eq!(target_scores(), vec![(20, 600), (11, 200), (10, 200)]); }) } #[test] fn on_nominator_update_lazy_voter_works() { - ExtBuilder::default().populate_lists().build_and_execute(|| { - // sets voter list to lazy mode. - VoterListMode::::set(SortingMode::Lazy); - - assert_eq!( - get_scores::(), - vec![(10, 100), (11, 100), (1, 100), (2, 100)] - ); - assert_eq!(get_scores::(), vec![(10, 300), (11, 200)]); - - add_validator(20, 500); - // removes nomination from 10 and adds nomination to new validator, 20. - update_nominations_of(2, vec![11, 20]); - - // voter list has been updated because a new voter (20) has been added, not stake - // updated. - assert_eq!( - get_scores::(), - [(20, 500), (10, 100), (11, 100), (1, 100), (2, 100)] - ); - - // target list has been updated: - assert_eq!(get_scores::(), vec![(20, 600), (11, 200), (10, 200)]); - }) + ExtBuilder::default() + .populate_lists() + .voter_update_mode(crate::VoterUpdateMode::Lazy) + .build_and_execute(|| { + assert_eq!(voter_scores(), vec![(10, 100), (11, 100), (1, 100), (2, 100)]); + assert_eq!(target_scores(), vec![(10, 300), (11, 200)]); + + add_validator(20, 500); + // removes nomination from 10 and adds nomination to new validator, 20. + update_nominations_of(2, vec![11, 20]); + + // even in lazy mode, the new voter node is inserted. + assert_eq!(voter_scores(), [(20, 500), (10, 100), (11, 100), (1, 100), (2, 100)]); + + // target list has been updated: + assert_eq!(target_scores(), vec![(20, 600), (11, 200), (10, 200)]); + }) } #[test] diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 53d482811eae..7a61c452b6e7 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -73,6 +73,12 @@ pub enum StakerStatus { Nominator(Vec), } +impl StakerStatus { + pub fn is_nominator(&self) -> bool { + matches!(self, Self::Nominator(_)) + } +} + /// A struct that reflects stake that an account has in the staking system. Provides a set of /// methods to operate on it's properties. Aimed at making `StakingInterface` more concise. #[derive(RuntimeDebug, Clone, Copy, Eq, PartialEq, Default)]