// 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. use super::*; use crate as root_offences; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, onchain, SequentialPhragmen, }; use frame_support::{ derive_impl, parameter_types, traits::{ConstU32, ConstU64, Hooks, OneSessionHandler}, }; use pallet_staking::StakerStatus; use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; use sp_staking::{EraIndex, SessionIndex}; use sp_std::collections::btree_map::BTreeMap; type Block = frame_system::mocking::MockBlock; type AccountId = u64; type Balance = u64; type BlockNumber = u64; pub const INIT_TIMESTAMP: u64 = 30_000; pub const BLOCK_TIME: u64 = 1000; frame_support::construct_runtime!( pub enum Test { System: frame_system, Timestamp: pallet_timestamp, Balances: pallet_balances, Staking: pallet_staking, Session: pallet_session, RootOffences: root_offences, Historical: pallet_session::historical, } ); /// 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; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type Block = Block; type AccountData = pallet_balances::AccountData; } impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = Balance; type RuntimeEvent = RuntimeEvent; type DustRemoval = (); type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); type FreezeIdentifier = (); type MaxFreezes = (); type RuntimeHoldReason = (); type RuntimeFreezeReason = (); } pallet_staking_reward_curve::build! { const REWARD_CURVE: PiecewiseLinear<'static> = curve!( min_inflation: 0_025_000u64, 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 static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); } pub struct OnChainSeqPhragmen; impl onchain::Config for OnChainSeqPhragmen { type System = Test; type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = (); type MaxWinners = ConstU32<100>; type Bounds = ElectionsBounds; } parameter_types! { pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub static Offset: BlockNumber = 0; pub const Period: BlockNumber = 1; pub static SessionsPerEra: SessionIndex = 3; pub static SlashDeferDuration: EraIndex = 0; pub const BondingDuration: EraIndex = 3; pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(75); } impl pallet_staking::Config for Test { type Currency = Balances; type CurrencyBalance = ::Balance; type UnixTime = Timestamp; type CurrencyToVote = (); type RewardRemainder = (); type RuntimeEvent = RuntimeEvent; type Slash = (); type Reward = (); type SessionsPerEra = SessionsPerEra; type SlashDeferDuration = SlashDeferDuration; type AdminOrigin = frame_system::EnsureRoot; type BondingDuration = BondingDuration; type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; type MaxExposurePageSize = ConstU32<64>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; type TargetList = pallet_staking::UseValidatorsMap; type NominationsQuota = pallet_staking::FixedNominationsQuota<16>; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; type MaxControllersInDeprecationBatch = ConstU32<100>; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } impl pallet_session::historical::Config for Test { type FullIdentification = pallet_staking::Exposure; type FullIdentificationOf = pallet_staking::ExposureOf; } 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 = pallet_staking::StashOf; type NextSessionRotation = pallet_session::PeriodicSessions; type WeightInfo = (); } impl pallet_timestamp::Config for Test { type Moment = u64; type OnTimestampSet = (); type MinimumPeriod = ConstU64<5>; type WeightInfo = (); } impl Config for Test { type RuntimeEvent = RuntimeEvent; } pub struct ExtBuilder { validator_count: u32, minimum_validator_count: u32, invulnerables: Vec, balance_factor: Balance, } impl Default for ExtBuilder { fn default() -> Self { Self { validator_count: 2, minimum_validator_count: 0, invulnerables: vec![], balance_factor: 1, } } } impl ExtBuilder { fn build(self) -> sp_io::TestExternalities { let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![ // controllers (still used in some tests. Soon to be deprecated). (10, self.balance_factor * 50), (20, self.balance_factor * 50), (30, self.balance_factor * 50), (40, self.balance_factor * 50), // stashes (11, self.balance_factor * 1000), (21, self.balance_factor * 1000), (31, self.balance_factor * 500), (41, self.balance_factor * 1000), ], } .assimilate_storage(&mut storage) .unwrap(); let stakers = vec![ // (stash, ctrl, stake, status) // these two will be elected in the default test where we elect 2. (11, 11, 1000, StakerStatus::::Validator), (21, 21, 1000, StakerStatus::::Validator), // a loser validator (31, 31, 500, StakerStatus::::Validator), // an idle validator (41, 41, 1000, StakerStatus::::Idle), ]; let _ = pallet_staking::GenesisConfig:: { stakers: stakers.clone(), ..Default::default() }; 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), ..Default::default() } .assimilate_storage(&mut storage); let _ = pallet_session::GenesisConfig:: { keys: stakers .into_iter() .map(|(id, ..)| (id, id, SessionKeys { other: id.into() })) .collect(), } .assimilate_storage(&mut storage); storage.into() } pub fn build_and_execute(self, test: impl FnOnce() -> ()) { let mut ext = self.build(); ext.execute_with(test); } } /// 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, ); } /// 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()); } } } pub(crate) fn active_era() -> EraIndex { Staking::active_era().unwrap().index }