// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Test utilities use crate::{self as pallet_staking, *}; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, onchain, SequentialPhragmen, VoteWeight, }; use frame_support::{ assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::{ ConstU64, Currency, EitherOfDiverse, FindAuthor, Get, Hooks, Imbalance, LockableCurrency, OnUnbalanced, OneSessionHandler, WithdrawReasons, }, weights::constants::RocksDbWeight, }; use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_io; use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; use sp_staking::{ offence::{OffenceDetails, OnOffenceHandler}, OnStakingUpdate, }; pub const INIT_TIMESTAMP: u64 = 30_000; pub const BLOCK_TIME: u64 = 1000; /// The AccountId alias in this test module. pub(crate) type AccountId = u64; pub(crate) type BlockNumber = u64; pub(crate) type Balance = u128; /// Another session handler struct to test on_disabled. pub struct OtherSessionHandler; impl OneSessionHandler for OtherSessionHandler { type Key = UintAuthorityId; fn on_genesis_session<'a, I: 'a>(_: I) where I: Iterator, AccountId: 'a, { } fn on_new_session<'a, I: 'a>(_: bool, _: I, _: I) where I: Iterator, AccountId: 'a, { } fn on_disabled(_validator_index: u32) {} } impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { type Public = UintAuthorityId; } pub fn is_disabled(controller: AccountId) -> bool { let stash = Ledger::::get(&controller).unwrap().stash; let validator_index = match Session::validators().iter().position(|v| *v == stash) { Some(index) => index as u32, None => return false, }; Session::disabled_validators().contains(&validator_index) } type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( pub enum Test { System: frame_system, Authorship: pallet_authorship, Timestamp: pallet_timestamp, Balances: pallet_balances, Staking: pallet_staking, Session: pallet_session, Historical: pallet_session::historical, VoterBagsList: pallet_bags_list::, } ); /// Author of block is always 11 pub struct Author11; impl FindAuthor for Author11 { fn find_author<'a, I>(_digests: I) -> Option where I: 'a + IntoIterator, { Some(11) } } parameter_types! { pub static SessionsPerEra: SessionIndex = 3; pub static ExistentialDeposit: Balance = 1; pub static SlashDeferDuration: EraIndex = 0; pub static Period: BlockNumber = 5; pub static Offset: BlockNumber = 0; pub static MaxControllersInDeprecationBatch: u32 = 5900; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type DbWeight = RocksDbWeight; type Block = Block; type AccountData = pallet_balances::AccountData; } impl pallet_balances::Config for Test { type MaxLocks = frame_support::traits::ConstU32<1024>; type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = Balance; type RuntimeEvent = RuntimeEvent; type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); type FreezeIdentifier = (); type MaxFreezes = (); type RuntimeHoldReason = (); type RuntimeFreezeReason = (); } sp_runtime::impl_opaque_keys! { pub struct SessionKeys { pub other: OtherSessionHandler, } } impl pallet_session::Config for Test { type SessionManager = pallet_session::historical::NoteHistoricalRoot; type Keys = SessionKeys; type ShouldEndSession = pallet_session::PeriodicSessions; type SessionHandler = (OtherSessionHandler,); type RuntimeEvent = RuntimeEvent; type ValidatorId = AccountId; type ValidatorIdOf = crate::StashOf; type NextSessionRotation = pallet_session::PeriodicSessions; type WeightInfo = (); } impl pallet_session::historical::Config for Test { type FullIdentification = crate::Exposure; type FullIdentificationOf = crate::ExposureOf; } impl pallet_authorship::Config for Test { type FindAuthor = Author11; type EventHandler = Pallet; } impl pallet_timestamp::Config for Test { type Moment = u64; type OnTimestampSet = (); type MinimumPeriod = ConstU64<5>; type WeightInfo = (); } pallet_staking_reward_curve::build! { const I_NPOS: PiecewiseLinear<'static> = curve!( min_inflation: 0_025_000, max_inflation: 0_100_000, ideal_stake: 0_500_000, falloff: 0_050_000, max_piece_count: 40, test_precision: 0_005_000, ); } parameter_types! { pub const BondingDuration: EraIndex = 3; pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS; } parameter_types! { pub static RewardRemainderUnbalanced: u128 = 0; } pub struct RewardRemainderMock; impl OnUnbalanced> for RewardRemainderMock { fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { RewardRemainderUnbalanced::mutate(|v| { *v += amount.peek(); }); drop(amount); } } const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; parameter_types! { pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; pub static HistoryDepth: u32 = 80; pub static MaxExposurePageSize: u32 = 64; pub static MaxUnlockingChunks: u32 = 32; pub static RewardOnUnbalanceWasCalled: bool = false; pub static MaxWinners: u32 = 100; pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); pub static AbsoluteMaxNominations: u32 = 16; } type VoterBagsListInstance = pallet_bags_list::Instance1; impl pallet_bags_list::Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); // Staking is the source of truth for voter bags list, since they are not kept up to date. type ScoreProvider = Staking; type BagThresholds = BagThresholds; type Score = VoteWeight; } pub struct OnChainSeqPhragmen; impl onchain::Config for OnChainSeqPhragmen { type System = Test; type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = (); type MaxWinners = MaxWinners; type Bounds = ElectionsBounds; } pub struct MockReward {} impl OnUnbalanced> for MockReward { fn on_unbalanced(_: PositiveImbalanceOf) { RewardOnUnbalanceWasCalled::set(true); } } parameter_types! { pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); pub static SlashObserver: BTreeMap> = BTreeMap::new(); } pub struct EventListenerMock; impl OnStakingUpdate for EventListenerMock { fn on_slash( pool_account: &AccountId, slashed_bonded: Balance, slashed_chunks: &BTreeMap, total_slashed: Balance, ) { LedgerSlashPerEra::set((slashed_bonded, slashed_chunks.clone())); SlashObserver::mutate(|map| { map.insert(*pool_account, map.get(pool_account).unwrap_or(&0) + total_slashed) }); } } // Disabling threshold for `UpToLimitDisablingStrategy` pub(crate) const DISABLING_LIMIT_FACTOR: usize = 3; impl crate::pallet::pallet::Config for Test { type Currency = Balances; type CurrencyBalance = ::Balance; type UnixTime = Timestamp; type CurrencyToVote = (); type RewardRemainder = RewardRemainderMock; type RuntimeEvent = RuntimeEvent; type Slash = (); type Reward = MockReward; type SessionsPerEra = SessionsPerEra; type SlashDeferDuration = SlashDeferDuration; type AdminOrigin = EnsureOneOrRoot; type BondingDuration = BondingDuration; type SessionInterface = Self; type EraPayout = ConvertCurve; type NextNewSession = Session; type MaxExposurePageSize = MaxExposurePageSize; type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. type VoterList = VoterBagsList; type TargetList = UseValidatorsMap; type NominationsQuota = WeightedNominationsQuota<16>; type MaxUnlockingChunks = MaxUnlockingChunks; type HistoryDepth = HistoryDepth; type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch; type EventListeners = EventListenerMock; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } pub struct WeightedNominationsQuota; impl NominationsQuota for WeightedNominationsQuota where u128: From, { type MaxNominations = AbsoluteMaxNominations; fn curve(balance: Balance) -> u32 { match balance.into() { // random curve for testing. 0..=110 => MAX, 111 => 0, 222 => 2, 333 => MAX + 10, _ => MAX, } } } pub(crate) type StakingCall = crate::Call; pub(crate) type TestCall = ::RuntimeCall; parameter_types! { // if true, skips the try-state for the test running. pub static SkipTryStateCheck: bool = false; } pub struct ExtBuilder { nominate: bool, validator_count: u32, minimum_validator_count: u32, invulnerables: Vec, has_stakers: bool, initialize_first_session: bool, pub min_nominator_bond: Balance, min_validator_bond: Balance, balance_factor: Balance, status: BTreeMap>, stakes: BTreeMap, stakers: Vec<(AccountId, AccountId, Balance, StakerStatus)>, } impl Default for ExtBuilder { fn default() -> Self { Self { nominate: true, validator_count: 2, minimum_validator_count: 0, balance_factor: 1, invulnerables: vec![], has_stakers: true, initialize_first_session: true, min_nominator_bond: ExistentialDeposit::get(), min_validator_bond: ExistentialDeposit::get(), status: Default::default(), stakes: Default::default(), stakers: Default::default(), } } } impl ExtBuilder { pub fn existential_deposit(self, existential_deposit: Balance) -> Self { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = existential_deposit); self } pub fn nominate(mut self, nominate: bool) -> Self { self.nominate = nominate; self } pub fn validator_count(mut self, count: u32) -> Self { self.validator_count = count; self } pub fn minimum_validator_count(mut self, count: u32) -> Self { self.minimum_validator_count = count; self } pub fn slash_defer_duration(self, eras: EraIndex) -> Self { SLASH_DEFER_DURATION.with(|v| *v.borrow_mut() = eras); self } pub fn invulnerables(mut self, invulnerables: Vec) -> Self { self.invulnerables = invulnerables; self } pub fn session_per_era(self, length: SessionIndex) -> Self { SESSIONS_PER_ERA.with(|v| *v.borrow_mut() = length); self } pub fn period(self, length: BlockNumber) -> Self { PERIOD.with(|v| *v.borrow_mut() = length); self } pub fn has_stakers(mut self, has: bool) -> Self { self.has_stakers = has; self } pub fn initialize_first_session(mut self, init: bool) -> Self { self.initialize_first_session = init; self } pub fn offset(self, offset: BlockNumber) -> Self { OFFSET.with(|v| *v.borrow_mut() = offset); self } pub fn min_nominator_bond(mut self, amount: Balance) -> Self { self.min_nominator_bond = amount; self } pub fn min_validator_bond(mut self, amount: Balance) -> Self { self.min_validator_bond = amount; self } pub fn set_status(mut self, who: AccountId, status: StakerStatus) -> Self { self.status.insert(who, status); self } pub fn set_stake(mut self, who: AccountId, stake: Balance) -> Self { self.stakes.insert(who, stake); self } pub fn add_staker( mut self, stash: AccountId, ctrl: AccountId, stake: Balance, status: StakerStatus, ) -> Self { self.stakers.push((stash, ctrl, stake, status)); self } pub fn balance_factor(mut self, factor: Balance) -> Self { self.balance_factor = factor; self } pub fn try_state(self, enable: bool) -> Self { SkipTryStateCheck::set(!enable); self } fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); let _ = pallet_balances::GenesisConfig:: { balances: vec![ (1, 10 * self.balance_factor), (2, 20 * self.balance_factor), (3, 300 * self.balance_factor), (4, 400 * self.balance_factor), // controllers (still used in some tests. Soon to be deprecated). (10, self.balance_factor), (20, self.balance_factor), (30, self.balance_factor), (40, self.balance_factor), (50, self.balance_factor), // stashes (11, self.balance_factor * 1000), (21, self.balance_factor * 2000), (31, self.balance_factor * 2000), (41, self.balance_factor * 2000), (51, self.balance_factor * 2000), (201, self.balance_factor * 2000), (202, self.balance_factor * 2000), // optional nominator (100, self.balance_factor * 2000), (101, self.balance_factor * 2000), // aux accounts (60, self.balance_factor), (61, self.balance_factor * 2000), (70, self.balance_factor), (71, self.balance_factor * 2000), (80, self.balance_factor), (81, self.balance_factor * 2000), // This allows us to have a total_payout different from 0. (999, 1_000_000_000_000), ], } .assimilate_storage(&mut storage); let mut stakers = vec![]; if self.has_stakers { stakers = vec![ // (stash, ctrl, stake, status) // these two will be elected in the default test where we elect 2. (11, 11, self.balance_factor * 1000, StakerStatus::::Validator), (21, 21, self.balance_factor * 1000, StakerStatus::::Validator), // a loser validator (31, 31, self.balance_factor * 500, StakerStatus::::Validator), // an idle validator (41, 41, self.balance_factor * 1000, StakerStatus::::Idle), (51, 51, self.balance_factor * 1000, StakerStatus::::Idle), (201, 201, self.balance_factor * 1000, StakerStatus::::Idle), (202, 202, self.balance_factor * 1000, StakerStatus::::Idle), ]; // optionally add a nominator if self.nominate { stakers.push(( 101, 101, self.balance_factor * 500, StakerStatus::::Nominator(vec![11, 21]), )) } // replace any of the status if needed. self.status.into_iter().for_each(|(stash, status)| { let (_, _, _, ref mut prev_status) = stakers .iter_mut() .find(|s| s.0 == stash) .expect("set_status staker should exist; qed"); *prev_status = status; }); // replaced any of the stakes if needed. self.stakes.into_iter().for_each(|(stash, stake)| { let (_, _, ref mut prev_stake, _) = stakers .iter_mut() .find(|s| s.0 == stash) .expect("set_stake staker should exits; qed."); *prev_stake = stake; }); // extend stakers if needed. stakers.extend(self.stakers) } let _ = pallet_staking::GenesisConfig:: { stakers: stakers.clone(), validator_count: self.validator_count, minimum_validator_count: self.minimum_validator_count, invulnerables: self.invulnerables, slash_reward_fraction: Perbill::from_percent(10), min_nominator_bond: self.min_nominator_bond, min_validator_bond: self.min_validator_bond, ..Default::default() } .assimilate_storage(&mut storage); let _ = pallet_session::GenesisConfig:: { keys: if self.has_stakers { // set the keys for the first session. stakers .into_iter() .map(|(id, ..)| (id, id, SessionKeys { other: id.into() })) .collect() } else { // set some dummy validators in genesis. (0..self.validator_count as u64) .map(|id| (id, id, SessionKeys { other: id.into() })) .collect() }, } .assimilate_storage(&mut storage); let mut ext = sp_io::TestExternalities::from(storage); if self.initialize_first_session { // We consider all test to start after timestamp is initialized This must be ensured by // having `timestamp::on_initialize` called before `staking::on_initialize`. Also, if // session length is 1, then it is already triggered. ext.execute_with(|| { System::set_block_number(1); Session::on_initialize(1); >::on_initialize(1); Timestamp::set_timestamp(INIT_TIMESTAMP); }); } ext } pub fn build_and_execute(self, test: impl FnOnce() -> ()) { sp_tracing::try_init_simple(); let mut ext = self.build(); ext.execute_with(test); ext.execute_with(|| { if !SkipTryStateCheck::get() { Staking::do_try_state(System::block_number()).unwrap(); } }); } } pub(crate) fn active_era() -> EraIndex { Staking::active_era().unwrap().index } pub(crate) fn current_era() -> EraIndex { Staking::current_era().unwrap() } pub(crate) fn bond(who: AccountId, val: Balance) { let _ = Balances::make_free_balance_be(&who, val); assert_ok!(Staking::bond(RuntimeOrigin::signed(who), val, RewardDestination::Stash)); } pub(crate) fn bond_validator(who: AccountId, val: Balance) { bond(who, val); assert_ok!(Staking::validate(RuntimeOrigin::signed(who), ValidatorPrefs::default())); assert_ok!(Session::set_keys( RuntimeOrigin::signed(who), SessionKeys { other: who.into() }, vec![] )); } pub(crate) fn bond_nominator(who: AccountId, val: Balance, target: Vec) { bond(who, val); assert_ok!(Staking::nominate(RuntimeOrigin::signed(who), target)); } pub(crate) fn bond_virtual_nominator( who: AccountId, payee: AccountId, val: Balance, target: Vec, ) { // In a real scenario, `who` is a keyless account managed by another pallet which provides for // it. System::inc_providers(&who); // Bond who virtually. assert_ok!(::virtual_bond(&who, val, &payee)); assert_ok!(Staking::nominate(RuntimeOrigin::signed(who), target)); } /// Progress to the given block, triggering session and era changes as we progress. /// /// This will finalize the previous block, initialize up to the given block, essentially simulating /// a block import/propose process where we first initialize the block, then execute some stuff (not /// in the function), and then finalize the block. pub(crate) fn run_to_block(n: BlockNumber) { Staking::on_finalize(System::block_number()); for b in (System::block_number() + 1)..=n { System::set_block_number(b); Session::on_initialize(b); >::on_initialize(b); Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); if b != n { Staking::on_finalize(System::block_number()); } } } /// Progresses from the current block number (whatever that may be) to the `P * session_index + 1`. pub(crate) fn start_session(session_index: SessionIndex) { let end: u64 = if Offset::get().is_zero() { (session_index as u64) * Period::get() } else { Offset::get() + (session_index.saturating_sub(1) as u64) * Period::get() }; run_to_block(end); // session must have progressed properly. assert_eq!( Session::current_index(), session_index, "current session index = {}, expected = {}", Session::current_index(), session_index, ); } /// Go one session forward. pub(crate) fn advance_session() { let current_index = Session::current_index(); start_session(current_index + 1); } /// Progress until the given era. pub(crate) fn start_active_era(era_index: EraIndex) { start_session((era_index * >::get()).into()); assert_eq!(active_era(), era_index); // One way or another, current_era must have changed before the active era, so they must match // at this point. assert_eq!(current_era(), active_era()); } pub(crate) fn current_total_payout_for_duration(duration: u64) -> Balance { let (payout, _rest) = ::EraPayout::era_payout( Staking::eras_total_stake(active_era()), Balances::total_issuance(), duration, ); assert!(payout > 0); payout } pub(crate) fn maximum_payout_for_duration(duration: u64) -> Balance { let (payout, rest) = ::EraPayout::era_payout( Staking::eras_total_stake(active_era()), Balances::total_issuance(), duration, ); payout + rest } /// Time it takes to finish a session. /// /// Note, if you see `time_per_session() - BLOCK_TIME`, it is fine. This is because we set the /// timestamp after on_initialize, so the timestamp is always one block old. pub(crate) fn time_per_session() -> u64 { Period::get() * BLOCK_TIME } /// Time it takes to finish an era. /// /// Note, if you see `time_per_era() - BLOCK_TIME`, it is fine. This is because we set the /// timestamp after on_initialize, so the timestamp is always one block old. pub(crate) fn time_per_era() -> u64 { time_per_session() * SessionsPerEra::get() as u64 } /// Time that will be calculated for the reward per era. pub(crate) fn reward_time_per_era() -> u64 { time_per_era() - BLOCK_TIME } pub(crate) fn reward_all_elected() { let rewards = ::SessionInterface::validators().into_iter().map(|v| (v, 1)); >::reward_by_ids(rewards) } pub(crate) fn validator_controllers() -> Vec { Session::validators() .into_iter() .map(|s| Staking::bonded(&s).expect("no controller for validator")) .collect() } pub(crate) fn on_offence_in_era( offenders: &[OffenceDetails< AccountId, pallet_session::historical::IdentificationTuple, >], slash_fraction: &[Perbill], era: EraIndex, ) { let bonded_eras = crate::BondedEras::::get(); for &(bonded_era, start_session) in bonded_eras.iter() { if bonded_era == era { let _ = Staking::on_offence(offenders, slash_fraction, start_session); return } else if bonded_era > era { break } } if Staking::active_era().unwrap().index == era { let _ = Staking::on_offence( offenders, slash_fraction, Staking::eras_start_session_index(era).unwrap(), ); } else { panic!("cannot slash in era {}", era); } } pub(crate) fn on_offence_now( offenders: &[OffenceDetails< AccountId, pallet_session::historical::IdentificationTuple, >], slash_fraction: &[Perbill], ) { let now = Staking::active_era().unwrap().index; on_offence_in_era(offenders, slash_fraction, now) } pub(crate) fn add_slash(who: &AccountId) { on_offence_now( &[OffenceDetails { offender: (*who, Staking::eras_stakers(active_era(), who)), reporters: vec![], }], &[Perbill::from_percent(10)], ); } /// Make all validator and nominator request their payment pub(crate) fn make_all_reward_payment(era: EraIndex) { let validators_with_reward = ErasRewardPoints::::get(era) .individual .keys() .cloned() .collect::>(); // reward validators for validator_controller in validators_with_reward.iter().filter_map(Staking::bonded) { let ledger = >::get(&validator_controller).unwrap(); for page in 0..EraInfo::::get_page_count(era, &ledger.stash) { assert_ok!(Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), ledger.stash, era, page )); } } } pub(crate) fn bond_controller_stash(controller: AccountId, stash: AccountId) -> Result<(), String> { >::get(&stash).map_or(Ok(()), |_| Err("stash already bonded"))?; >::get(&controller).map_or(Ok(()), |_| Err("controller already bonded"))?; >::insert(stash, controller); >::insert(controller, StakingLedger::::default_from(stash)); Ok(()) } // simulates `set_controller` without corrupted ledger checks for testing purposes. pub(crate) fn set_controller_no_checks(stash: &AccountId) { let controller = Bonded::::get(stash).expect("testing stash should be bonded"); let ledger = Ledger::::get(&controller).expect("testing ledger should exist"); Ledger::::remove(&controller); Ledger::::insert(stash, ledger); Bonded::::insert(stash, stash); } // simulates `bond_extra` without corrupted ledger checks for testing purposes. pub(crate) fn bond_extra_no_checks(stash: &AccountId, amount: Balance) { let controller = Bonded::::get(stash).expect("bond must exist to bond_extra"); let mut ledger = Ledger::::get(&controller).expect("ledger must exist to bond_extra"); let new_total = ledger.total + amount; Balances::set_lock(crate::STAKING_ID, stash, new_total, WithdrawReasons::all()); ledger.total = new_total; ledger.active = new_total; Ledger::::insert(controller, ledger); } pub(crate) fn setup_double_bonded_ledgers() { let init_ledgers = Ledger::::iter().count(); let _ = Balances::make_free_balance_be(&333, 2000); let _ = Balances::make_free_balance_be(&444, 2000); let _ = Balances::make_free_balance_be(&555, 2000); let _ = Balances::make_free_balance_be(&777, 2000); assert_ok!(Staking::bond(RuntimeOrigin::signed(333), 10, RewardDestination::Staked)); assert_ok!(Staking::bond(RuntimeOrigin::signed(444), 20, RewardDestination::Staked)); assert_ok!(Staking::bond(RuntimeOrigin::signed(555), 20, RewardDestination::Staked)); // not relevant to the test case, but ensures try-runtime checks pass. [333, 444, 555] .iter() .for_each(|s| Payee::::insert(s, RewardDestination::Staked)); // we want to test the case where a controller can also be a stash of another ledger. // for that, we change the controller/stash bonding so that: // * 444 becomes controller of 333. // * 555 becomes controller of 444. // * 777 becomes controller of 555. let ledger_333 = Ledger::::get(333).unwrap(); let ledger_444 = Ledger::::get(444).unwrap(); let ledger_555 = Ledger::::get(555).unwrap(); // 777 becomes controller of 555. Bonded::::mutate(555, |controller| *controller = Some(777)); Ledger::::insert(777, ledger_555); // 555 becomes controller of 444. Bonded::::mutate(444, |controller| *controller = Some(555)); Ledger::::insert(555, ledger_444); // 444 becomes controller of 333. Bonded::::mutate(333, |controller| *controller = Some(444)); Ledger::::insert(444, ledger_333); // 333 is not controller anymore. Ledger::::remove(333); // checks. now we have: // * +3 ledgers assert_eq!(Ledger::::iter().count(), 3 + init_ledgers); // * stash 333 has controller 444. assert_eq!(Bonded::::get(333), Some(444)); assert_eq!(StakingLedger::::paired_account(StakingAccount::Stash(333)), Some(444)); assert_eq!(Ledger::::get(444).unwrap().stash, 333); // * stash 444 has controller 555. assert_eq!(Bonded::::get(444), Some(555)); assert_eq!(StakingLedger::::paired_account(StakingAccount::Stash(444)), Some(555)); assert_eq!(Ledger::::get(555).unwrap().stash, 444); // * stash 555 has controller 777. assert_eq!(Bonded::::get(555), Some(777)); assert_eq!(StakingLedger::::paired_account(StakingAccount::Stash(555)), Some(777)); assert_eq!(Ledger::::get(777).unwrap().stash, 555); } #[macro_export] macro_rules! assert_session_era { ($session:expr, $era:expr) => { assert_eq!( Session::current_index(), $session, "wrong session {} != {}", Session::current_index(), $session, ); assert_eq!( Staking::current_era().unwrap(), $era, "wrong current era {} != {}", Staking::current_era().unwrap(), $era, ); }; } pub(crate) fn staking_events() -> Vec> { System::events() .into_iter() .map(|r| r.event) .filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None }) .collect() } parameter_types! { static StakingEventsIndex: usize = 0; } ord_parameter_types! { pub const One: u64 = 1; } type EnsureOneOrRoot = EitherOfDiverse, EnsureSignedBy>; pub(crate) fn staking_events_since_last_call() -> Vec> { let all: Vec<_> = System::events() .into_iter() .filter_map(|r| if let RuntimeEvent::Staking(inner) = r.event { Some(inner) } else { None }) .collect(); let seen = StakingEventsIndex::get(); StakingEventsIndex::set(all.len()); all.into_iter().skip(seen).collect() } pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) { (Balances::free_balance(who), Balances::reserved_balance(who)) }