diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 1a4086ad2ab11..830b33ceb69a2 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -704,6 +704,9 @@ impl Pallet { /// /// `maybe_max_len` can imposes a cap on the number of voters returned; /// + /// Sets `MinimumActiveStake` to the minimum active nominator stake in the returned set of + /// nominators. + /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. pub fn get_npos_voters(maybe_max_len: Option) -> Vec> { let max_allowed_len = { @@ -719,6 +722,7 @@ impl Pallet { let mut voters_seen = 0u32; let mut validators_taken = 0u32; let mut nominators_taken = 0u32; + let mut min_active_stake = u64::MAX; let mut sorted_voters = T::VoterList::iter(); while all_voters.len() < max_allowed_len && @@ -733,12 +737,15 @@ impl Pallet { }; if let Some(Nominations { targets, .. }) = >::get(&voter) { + let voter_weight = weight_of(&voter); if !targets.is_empty() { - all_voters.push((voter.clone(), weight_of(&voter), targets)); + all_voters.push((voter.clone(), voter_weight, targets)); nominators_taken.saturating_inc(); } else { // Technically should never happen, but not much we can do about it. } + min_active_stake = + if voter_weight < min_active_stake { voter_weight } else { min_active_stake }; } else if Validators::::contains_key(&voter) { // if this voter is a validator: let self_vote = ( @@ -769,6 +776,11 @@ impl Pallet { Self::register_weight(T::WeightInfo::get_npos_voters(validators_taken, nominators_taken)); + let min_active_stake: T::CurrencyBalance = + if all_voters.len() == 0 { 0u64.into() } else { min_active_stake.into() }; + + MinimumActiveStake::::put(min_active_stake); + log!( info, "generated {} npos voters, {} from validators and {} nominators", diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index fda455ca3c166..2daa992f4ef6e 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -295,6 +295,10 @@ pub mod pallet { #[pallet::storage] pub type MinValidatorBond = StorageValue<_, BalanceOf, ValueQuery>; + /// The minimum active nominator stake of the last successful election. + #[pallet::storage] + pub type MinimumActiveStake = StorageValue<_, BalanceOf, ValueQuery>; + /// The minimum amount of commission that validators can set. /// /// If set to `0`, no limit exists. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 3e0a62f53d886..74d8dc8a8105c 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4463,6 +4463,32 @@ mod election_data_provider { ); } + #[test] + fn set_minimum_active_stake_is_correct() { + ExtBuilder::default() + .nominate(false) + .add_staker(61, 60, 2_000, StakerStatus::::Nominator(vec![21])) + .add_staker(71, 70, 10, StakerStatus::::Nominator(vec![21])) + .add_staker(81, 80, 50, StakerStatus::::Nominator(vec![21])) + .build_and_execute(|| { + assert_ok!(::electing_voters(None)); + assert_eq!(MinimumActiveStake::::get(), 10); + + // remove staker with lower bond by limiting the number of voters and check + // `MinimumActiveStake` again after electing voters. + assert_ok!(::electing_voters(Some(5))); + assert_eq!(MinimumActiveStake::::get(), 50); + }); + } + + #[test] + fn set_minimum_active_stake_zero_correct() { + ExtBuilder::default().has_stakers(false).build_and_execute(|| { + assert_ok!(::electing_voters(None)); + assert_eq!(MinimumActiveStake::::get(), 0); + }); + } + #[test] fn voters_include_self_vote() { ExtBuilder::default().nominate(false).build_and_execute(|| {