// 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::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, traits::{ConstU32, ConstU64, GenesisBuild, Hooks, OneSessionHandler}, }; use pallet_staking::StakerStatus; use sp_core::H256; use sp_runtime::{ curve::PiecewiseLinear, testing::{Header, UintAuthorityId}, traits::{BlakeTwo256, IdentityLookup, Zero}, }; use sp_staking::{EraIndex, SessionIndex}; use sp_std::collections::btree_map::BTreeMap; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; 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 where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, Session: pallet_session::{Pallet, Call, Storage, Event, Config}, RootOffences: root_offences::{Pallet, Call, Storage, Event}, Historical: pallet_session::historical::{Pallet, Storage}, } ); /// 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; } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); type MaxConsumers = ConstU32<16>; } 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 MaxHolds = (); } 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, ); } pub struct OnChainSeqPhragmen; impl onchain::Config for OnChainSeqPhragmen { type System = Test; type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = (); type MaxWinners = ConstU32<100>; type VotersBound = ConstU32<{ u32::MAX }>; type TargetsBound = ConstU32<{ u32::MAX }>; } pub struct OnStakerSlashMock(core::marker::PhantomData); impl sp_staking::OnStakerSlash for OnStakerSlashMock { fn on_slash( _pool_account: &AccountId, slashed_bonded: Balance, slashed_chunks: &BTreeMap, ) { LedgerSlashPerEra::set((slashed_bonded, slashed_chunks.clone())); } } 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 MaxNominations = ConstU32<16>; type Currency = Balances; type CurrencyBalance = ::Balance; type UnixTime = Timestamp; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; 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 MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type OnStakerSlash = OnStakerSlashMock; 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 }