Use proper bounded vector type for nominations (#10601)

* Use proper bounded vector type for nominations

* add docs and tweak chill_other for cleanup purposes

* Fix the build

* remove TODO

* add a bit more doc

* even more docs
gushc

* Update frame/staking/src/pallet/mod.rs

Co-authored-by: Zeke Mostov <z.mostov@gmail.com>

* Update frame/staking/src/pallet/mod.rs

Co-authored-by: Zeke Mostov <z.mostov@gmail.com>

* Fix the nasty bug

* also bound the Snapshot type

* fix doc test

* document bounded_vec

* self-review

* remove unused

* Fix build

* frame-support: repetition overload for bounded_vec

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fix

* remove the need to allocate into unbounded voters etc etc

* Don't expect

* unbreal the build again

* handle macro a bit better

Co-authored-by: Zeke Mostov <z.mostov@gmail.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
This commit is contained in:
Kian Paimani
2022-01-25 15:44:10 +01:00
committed by GitHub
parent d94b5e32c5
commit 38d94d6323
34 changed files with 419 additions and 252 deletions
+14 -14
View File
@@ -355,17 +355,17 @@ benchmarks! {
kick {
// scenario: we want to kick `k` nominators from nominating us (we are a validator).
// we'll assume that `k` is under 128 for the purposes of determining the slope.
// each nominator should have `T::MAX_NOMINATIONS` validators nominated, and our validator
// each nominator should have `T::MaxNominations::get()` validators nominated, and our validator
// should be somewhere in there.
let k in 1 .. 128;
// these are the other validators; there are `T::MAX_NOMINATIONS - 1` of them, so
// there are a total of `T::MAX_NOMINATIONS` validators in the system.
let rest_of_validators = create_validators_with_seed::<T>(T::MAX_NOMINATIONS - 1, 100, 415)?;
// these are the other validators; there are `T::MaxNominations::get() - 1` of them, so
// there are a total of `T::MaxNominations::get()` validators in the system.
let rest_of_validators = create_validators_with_seed::<T>(T::MaxNominations::get() - 1, 100, 415)?;
// this is the validator that will be kicking.
let (stash, controller) = create_stash_controller::<T>(
T::MAX_NOMINATIONS - 1,
T::MaxNominations::get() - 1,
100,
Default::default(),
)?;
@@ -380,7 +380,7 @@ benchmarks! {
for i in 0 .. k {
// create a nominator stash.
let (n_stash, n_controller) = create_stash_controller::<T>(
T::MAX_NOMINATIONS + i,
T::MaxNominations::get() + i,
100,
Default::default(),
)?;
@@ -415,9 +415,9 @@ benchmarks! {
}
}
// Worst case scenario, T::MAX_NOMINATIONS
// Worst case scenario, T::MaxNominations::get()
nominate {
let n in 1 .. T::MAX_NOMINATIONS;
let n in 1 .. T::MaxNominations::get();
// clean up any existing state.
clear_validators_and_nominators::<T>();
@@ -428,7 +428,7 @@ benchmarks! {
// we are just doing an insert into the origin position.
let scenario = ListScenario::<T>::new(origin_weight, true)?;
let (stash, controller) = create_stash_controller_with_balance::<T>(
SEED + T::MAX_NOMINATIONS + 1, // make sure the account does not conflict with others
SEED + T::MaxNominations::get() + 1, // make sure the account does not conflict with others
origin_weight,
Default::default(),
).unwrap();
@@ -724,7 +724,7 @@ benchmarks! {
create_validators_with_nominators_for_era::<T>(
v,
n,
<T as Config>::MAX_NOMINATIONS as usize,
<T as Config>::MaxNominations::get() as usize,
false,
None,
)?;
@@ -742,7 +742,7 @@ benchmarks! {
create_validators_with_nominators_for_era::<T>(
v,
n,
<T as Config>::MAX_NOMINATIONS as usize,
<T as Config>::MaxNominations::get() as usize,
false,
None,
)?;
@@ -822,7 +822,7 @@ benchmarks! {
let s in 1 .. 20;
let validators = create_validators_with_nominators_for_era::<T>(
v, n, T::MAX_NOMINATIONS as usize, false, None
v, n, T::MaxNominations::get() as usize, false, None
)?
.into_iter()
.map(|v| T::Lookup::lookup(v).unwrap())
@@ -845,7 +845,7 @@ benchmarks! {
let n = MaxNominators::<T>::get();
let _ = create_validators_with_nominators_for_era::<T>(
v, n, T::MAX_NOMINATIONS as usize, false, None
v, n, T::MaxNominations::get() as usize, false, None
)?;
}: {
let targets = <Staking<T>>::get_npos_targets();
@@ -923,7 +923,7 @@ mod tests {
create_validators_with_nominators_for_era::<Test>(
v,
n,
<Test as Config>::MAX_NOMINATIONS as usize,
<Test as Config>::MaxNominations::get() as usize,
false,
None,
)
+6 -3
View File
@@ -303,6 +303,7 @@ use codec::{Decode, Encode, HasCompact};
use frame_support::{
traits::{ConstU32, Currency, Get},
weights::Weight,
BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
};
use scale_info::TypeInfo;
use sp_runtime::{
@@ -574,10 +575,12 @@ where
}
/// A record of the nominations made by a specific account.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct Nominations<AccountId> {
#[derive(PartialEqNoBound, EqNoBound, Clone, Encode, Decode, RuntimeDebugNoBound, TypeInfo)]
#[codec(mel_bound(T: Config))]
#[scale_info(skip_type_params(T))]
pub struct Nominations<T: Config> {
/// The targets of nomination.
pub targets: Vec<AccountId>,
pub targets: BoundedVec<T::AccountId, T::MaxNominations>,
/// The era the nominations were submitted.
///
/// Except for initial nominations which are considered submitted at era 0.
+3 -2
View File
@@ -234,6 +234,7 @@ const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] =
parameter_types! {
pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS;
pub static MaxNominations: u32 = 16;
}
impl pallet_bags_list::Config for Test {
@@ -249,7 +250,7 @@ impl onchain::Config for Test {
}
impl crate::pallet::pallet::Config for Test {
const MAX_NOMINATIONS: u32 = 16;
type MaxNominations = MaxNominations;
type Currency = Balances;
type UnixTime = Timestamp;
type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote;
@@ -533,7 +534,7 @@ fn post_conditions() {
}
fn check_count() {
let nominator_count = Nominators::<Test>::iter().count() as u32;
let nominator_count = Nominators::<Test>::iter_keys().count() as u32;
let validator_count = Validators::<Test>::iter().count() as u32;
assert_eq!(nominator_count, Nominators::<Test>::count());
assert_eq!(validator_count, Validators::<Test>::count());
+26 -15
View File
@@ -19,7 +19,7 @@
use frame_election_provider_support::{
data_provider, ElectionDataProvider, ElectionProvider, SortedListProvider, Supports,
VoteWeight, VoteWeightProvider,
VoteWeight, VoteWeightProvider, VoterOf,
};
use frame_support::{
pallet_prelude::*,
@@ -661,9 +661,7 @@ impl<T: Config> Pallet<T> {
///
/// All nominations that have been submitted before the last non-zero slash of the validator are
/// auto-chilled, but still count towards the limit imposed by `maybe_max_len`.
pub fn get_npos_voters(
maybe_max_len: Option<usize>,
) -> Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)> {
pub fn get_npos_voters(maybe_max_len: Option<usize>) -> Vec<VoterOf<Self>> {
let max_allowed_len = {
let nominator_count = Nominators::<T>::count() as usize;
let validator_count = Validators::<T>::count() as usize;
@@ -677,8 +675,13 @@ impl<T: Config> Pallet<T> {
let mut validators_taken = 0u32;
for (validator, _) in <Validators<T>>::iter().take(max_allowed_len) {
// Append self vote.
let self_vote =
(validator.clone(), Self::weight_of(&validator), vec![validator.clone()]);
let self_vote = (
validator.clone(),
Self::weight_of(&validator),
vec![validator.clone()]
.try_into()
.expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
);
all_voters.push(self_vote);
validators_taken.saturating_inc();
}
@@ -724,7 +727,12 @@ impl<T: Config> Pallet<T> {
nominators_taken.saturating_inc();
}
} else {
log!(error, "DEFENSIVE: invalid item in `SortedListProvider`: {:?}", nominator)
// this can only happen if: 1. there a pretty bad bug in the bags-list (or whatever
// is the sorted list) logic and the state of the two pallets is no longer
// compatible, or because the nominators is not decodable since they have more
// nomination than `T::MaxNominations`. This can rarely happen, and is not really an
// emergency or bug if it does.
log!(warn, "DEFENSIVE: invalid item in `SortedListProvider`: {:?}, this nominator probably has too many nominations now", nominator)
}
}
@@ -772,7 +780,7 @@ impl<T: Config> Pallet<T> {
/// NOTE: you must ALWAYS use this function to add nominator or update their targets. Any access
/// to `Nominators` or `VoterList` outside of this function is almost certainly
/// wrong.
pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T::AccountId>) {
pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T>) {
if !Nominators::<T>::contains_key(who) {
// maybe update sorted list. Error checking is defensive-only - this should never fail.
let _ = T::SortedListProvider::on_insert(who.clone(), Self::weight_of(who))
@@ -845,16 +853,14 @@ impl<T: Config> Pallet<T> {
impl<T: Config> ElectionDataProvider for Pallet<T> {
type AccountId = T::AccountId;
type BlockNumber = BlockNumberFor<T>;
const MAXIMUM_VOTES_PER_VOTER: u32 = T::MAX_NOMINATIONS;
type MaxVotesPerVoter = T::MaxNominations;
fn desired_targets() -> data_provider::Result<u32> {
Self::register_weight(T::DbWeight::get().reads(1));
Ok(Self::validator_count())
}
fn voters(
maybe_max_len: Option<usize>,
) -> data_provider::Result<Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)>> {
fn voters(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<VoterOf<Self>>> {
// This can never fail -- if `maybe_max_len` is `Some(_)` we handle it.
let voters = Self::get_npos_voters(maybe_max_len);
debug_assert!(maybe_max_len.map_or(true, |max| voters.len() <= max));
@@ -907,7 +913,11 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
}
#[cfg(feature = "runtime-benchmarks")]
fn add_voter(voter: T::AccountId, weight: VoteWeight, targets: Vec<T::AccountId>) {
fn add_voter(
voter: T::AccountId,
weight: VoteWeight,
targets: BoundedVec<T::AccountId, Self::MaxVotesPerVoter>,
) {
let stake = <BalanceOf<T>>::try_from(weight).unwrap_or_else(|_| {
panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
});
@@ -922,6 +932,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
claimed_rewards: vec![],
},
);
Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false });
}
@@ -957,7 +968,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
#[cfg(feature = "runtime-benchmarks")]
fn put_snapshot(
voters: Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)>,
voters: Vec<VoterOf<Self>>,
targets: Vec<T::AccountId>,
target_stake: Option<VoteWeight>,
) {
@@ -999,7 +1010,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
);
Self::do_add_nominator(
&v,
Nominations { targets: t, submitted_in: 0, suppressed: false },
Nominations { targets: t.try_into().unwrap(), submitted_in: 0, suppressed: false },
);
});
}
+53 -44
View File
@@ -32,7 +32,7 @@ use sp_runtime::{
DispatchError, Perbill, Percent,
};
use sp_staking::{EraIndex, SessionIndex};
use sp_std::{convert::From, prelude::*, result};
use sp_std::{convert::From, prelude::*};
mod impls;
@@ -50,6 +50,8 @@ const STAKING_ID: LockIdentifier = *b"staking ";
#[frame_support::pallet]
pub mod pallet {
use frame_election_provider_support::ElectionDataProvider;
use crate::BenchmarkingConfig;
use super::*;
@@ -94,7 +96,7 @@ pub mod pallet {
>;
/// Maximum number of nominations per nominator.
const MAX_NOMINATIONS: u32;
type MaxNominations: Get<u32>;
/// Tokens have been minted and are unused for validator-reward.
/// See [Era payout](./index.html#era-payout).
@@ -161,15 +163,6 @@ pub mod pallet {
type WeightInfo: WeightInfo;
}
#[pallet::extra_constants]
impl<T: Config> Pallet<T> {
// TODO: rename to snake case after https://github.com/paritytech/substrate/issues/8826 fixed.
#[allow(non_snake_case)]
fn MaxNominations() -> u32 {
T::MAX_NOMINATIONS
}
}
#[pallet::type_value]
pub(crate) fn HistoryDepthOnEmpty() -> u32 {
84u32
@@ -246,11 +239,26 @@ pub mod pallet {
#[pallet::storage]
pub type MaxValidatorsCount<T> = StorageValue<_, u32, OptionQuery>;
/// The map from nominator stash key to the set of stash keys of all validators to nominate.
/// The map from nominator stash key to their nomination preferences, namely the validators that
/// they wish to support.
///
/// Note that the keys of this storage map might become non-decodable in case the
/// [`Config::MaxNominations`] configuration is decreased. In this rare case, these nominators
/// are still existent in storage, their key is correct and retrievable (i.e. `contains_key`
/// indicates that they exist), but their value cannot be decoded. Therefore, the non-decodable
/// nominators will effectively not-exist, until they re-submit their preferences such that it
/// is within the bounds of the newly set `Config::MaxNominations`.
///
/// This implies that `::iter_keys().count()` and `::iter().count()` might return different
/// values for this map. Moreover, the main `::count()` is aligned with the former, namely the
/// number of keys that exist.
///
/// Lastly, if any of the nominators become non-decodable, they can be chilled immediately via
/// [`Call::chill_other`] dispatchable by anyone.
#[pallet::storage]
#[pallet::getter(fn nominators)]
pub type Nominators<T: Config> =
CountedStorageMap<_, Twox64Concat, T::AccountId, Nominations<T::AccountId>>;
CountedStorageMap<_, Twox64Concat, T::AccountId, Nominations<T>>;
/// The maximum nominator count before we stop allowing new validators to join.
///
@@ -681,6 +689,14 @@ pub mod pallet {
}
fn integrity_test() {
// ensure that we funnel the correct value to the `DataProvider::MaxVotesPerVoter`;
assert_eq!(
T::MaxNominations::get(),
<Self as ElectionDataProvider>::MaxVotesPerVoter::get()
);
// and that MaxNominations is always greater than 1, since we count on this.
assert!(!T::MaxNominations::get().is_zero());
sp_std::if_std! {
sp_io::TestExternalities::new_empty().execute_with(||
assert!(
@@ -978,7 +994,7 @@ pub mod pallet {
///
/// # <weight>
/// - The transaction's complexity is proportional to the size of `targets` (N)
/// which is capped at CompactAssignments::LIMIT (MAX_NOMINATIONS).
/// which is capped at CompactAssignments::LIMIT (T::MaxNominations).
/// - Both the reads and writes follow a similar pattern.
/// # </weight>
#[pallet::weight(T::WeightInfo::nominate(targets.len() as u32))]
@@ -1006,11 +1022,11 @@ pub mod pallet {
}
ensure!(!targets.is_empty(), Error::<T>::EmptyTargets);
ensure!(targets.len() <= T::MAX_NOMINATIONS as usize, Error::<T>::TooManyTargets);
ensure!(targets.len() <= T::MaxNominations::get() as usize, Error::<T>::TooManyTargets);
let old = Nominators::<T>::get(stash).map_or_else(Vec::new, |x| x.targets);
let old = Nominators::<T>::get(stash).map_or_else(Vec::new, |x| x.targets.into_inner());
let targets = targets
let targets: BoundedVec<_, _> = targets
.into_iter()
.map(|t| T::Lookup::lookup(t).map_err(DispatchError::from))
.map(|n| {
@@ -1022,11 +1038,13 @@ pub mod pallet {
}
})
})
.collect::<result::Result<Vec<T::AccountId>, _>>()?;
.collect::<Result<Vec<_>, _>>()?
.try_into()
.map_err(|_| Error::<T>::TooManyNominators)?;
let nominations = Nominations {
targets,
// Initial nominations are considered submitted at era 0. See `Nominations` doc
// Initial nominations are considered submitted at era 0. See `Nominations` doc.
submitted_in: Self::current_era().unwrap_or(0),
suppressed: false,
};
@@ -1216,11 +1234,6 @@ pub mod pallet {
/// Set the validators who cannot be slashed (if any).
///
/// The dispatch origin must be Root.
///
/// # <weight>
/// - O(V)
/// - Write: Invulnerables
/// # </weight>
#[pallet::weight(T::WeightInfo::set_invulnerables(invulnerables.len() as u32))]
pub fn set_invulnerables(
origin: OriginFor<T>,
@@ -1234,13 +1247,6 @@ pub mod pallet {
/// Force a current staker to become completely unstaked, immediately.
///
/// The dispatch origin must be Root.
///
/// # <weight>
/// O(S) where S is the number of slashing spans to be removed
/// Reads: Bonded, Slashing Spans, Account, Locks
/// Writes: Bonded, Slashing Spans (if S > 0), Ledger, Payee, Validators, Nominators,
/// Account, Locks Writes Each: SpanSlash * S
/// # </weight>
#[pallet::weight(T::WeightInfo::force_unstake(*num_slashing_spans))]
pub fn force_unstake(
origin: OriginFor<T>,
@@ -1266,11 +1272,6 @@ pub mod pallet {
/// The election process starts multiple blocks before the end of the era.
/// If this is called just before a new era is triggered, the election process may not
/// have enough blocks to get a result.
///
/// # <weight>
/// - Weight: O(1)
/// - Write: ForceEra
/// # </weight>
#[pallet::weight(T::WeightInfo::force_new_era_always())]
pub fn force_new_era_always(origin: OriginFor<T>) -> DispatchResult {
ensure_root(origin)?;
@@ -1283,14 +1284,6 @@ pub mod pallet {
/// Can be called by the `T::SlashCancelOrigin`.
///
/// Parameters: era and indices of the slashes for that era to kill.
///
/// # <weight>
/// Complexity: O(U + S)
/// with U unapplied slashes weighted with U=1000
/// and S is the number of slash indices to be canceled.
/// - Read: Unapplied Slashes
/// - Write: Unapplied Slashes
/// # </weight>
#[pallet::weight(T::WeightInfo::cancel_deferred_slash(slash_indices.len() as u32))]
pub fn cancel_deferred_slash(
origin: OriginFor<T>,
@@ -1550,6 +1543,11 @@ pub mod pallet {
///
/// If the caller is different than the controller being targeted, the following conditions
/// must be met:
///
/// * `controller` must belong to a nominator who has become non-decodable,
///
/// Or:
///
/// * A `ChillThreshold` must be set and checked which defines how close to the max
/// nominators or validators we must reach before users can start chilling one-another.
/// * A `MaxNominatorCount` and `MaxValidatorCount` must be set which is used to determine
@@ -1568,6 +1566,11 @@ pub mod pallet {
let stash = ledger.stash;
// In order for one user to chill another user, the following conditions must be met:
//
// * `controller` belongs to a nominator who has become non-decodable,
//
// Or
//
// * A `ChillThreshold` is set which defines how close to the max nominators or
// validators we must reach before users can start chilling one-another.
// * A `MaxNominatorCount` and `MaxValidatorCount` which is used to determine how close
@@ -1577,6 +1580,12 @@ pub mod pallet {
// threshold bond required.
//
// Otherwise, if caller is the same as the controller, this is just like `chill`.
if Nominators::<T>::contains_key(&stash) && Nominators::<T>::get(&stash).is_none() {
Self::chill_stash(&stash);
return Ok(())
}
if caller != controller {
let threshold = ChillThreshold::<T>::get().ok_or(Error::<T>::CannotChillOther)?;
let min_active_bond = if Nominators::<T>::contains_key(&stash) {
+99 -1
View File
@@ -4248,7 +4248,11 @@ fn count_check_works() {
Validators::<Test>::insert(987654321, ValidatorPrefs::default());
Nominators::<Test>::insert(
987654321,
Nominations { targets: vec![], submitted_in: Default::default(), suppressed: false },
Nominations {
targets: Default::default(),
submitted_in: Default::default(),
suppressed: false,
},
);
})
}
@@ -4589,6 +4593,100 @@ fn min_commission_works() {
})
}
#[test]
fn change_of_max_nominations() {
use frame_election_provider_support::ElectionDataProvider;
ExtBuilder::default()
.add_staker(60, 61, 10, StakerStatus::Nominator(vec![1]))
.add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3]))
.balance_factor(10)
.build_and_execute(|| {
// pre-condition
assert_eq!(MaxNominations::get(), 16);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(70, 3), (101, 2), (60, 1)]
);
// 3 validators and 3 nominators
assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3);
// abrupt change from 16 to 4, everyone should be fine.
MaxNominations::set(4);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(70, 3), (101, 2), (60, 1)]
);
assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3);
// abrupt change from 4 to 3, everyone should be fine.
MaxNominations::set(3);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(70, 3), (101, 2), (60, 1)]
);
assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3);
// abrupt change from 3 to 2, this should cause some nominators to be non-decodable, and
// thus non-existent unless if they update.
MaxNominations::set(2);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(101, 2), (60, 1)]
);
// 70 is still in storage..
assert!(Nominators::<Test>::contains_key(70));
// but its value cannot be decoded and default is returned.
assert!(Nominators::<Test>::get(70).is_none());
assert_eq!(Staking::voters(None).unwrap().len(), 3 + 2);
assert!(Nominators::<Test>::contains_key(101));
// abrupt change from 2 to 1, this should cause some nominators to be non-decodable, and
// thus non-existent unless if they update.
MaxNominations::set(1);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(60, 1)]
);
assert!(Nominators::<Test>::contains_key(70));
assert!(Nominators::<Test>::contains_key(60));
assert!(Nominators::<Test>::get(70).is_none());
assert!(Nominators::<Test>::get(60).is_some());
assert_eq!(Staking::voters(None).unwrap().len(), 3 + 1);
// now one of them can revive themselves by re-nominating to a proper value.
assert_ok!(Staking::nominate(Origin::signed(71), vec![1]));
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(70, 1), (60, 1)]
);
// or they can be chilled by any account.
assert!(Nominators::<Test>::contains_key(101));
assert!(Nominators::<Test>::get(101).is_none());
assert_ok!(Staking::chill_other(Origin::signed(70), 100));
assert!(!Nominators::<Test>::contains_key(101));
assert!(Nominators::<Test>::get(101).is_none());
})
}
mod sorted_list_provider {
use super::*;
use frame_election_provider_support::SortedListProvider;