diff --git a/Cargo.lock b/Cargo.lock index 679fa8d59aea2..32a8d569c66f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3931,6 +3931,7 @@ dependencies = [ "pallet-staking-reward-curve 2.0.0", "pallet-timestamp 2.0.0", "parity-scale-codec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "sp-core 2.0.0", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index fbf765fee526a..f8bd4c188bc3d 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -78,8 +78,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 198, - impl_version: 198, + spec_version: 199, + impl_version: 199, apis: RUNTIME_API_VERSIONS, }; diff --git a/frame/session/src/historical.rs b/frame/session/src/historical.rs index 1298111e930c8..077dbb0ea7f41 100644 --- a/frame/session/src/historical.rs +++ b/frame/session/src/historical.rs @@ -100,8 +100,9 @@ impl Module { /// Specialization of the crate-level `OnSessionEnding` which returns the old /// set of full identification when changing the validator set. pub trait OnSessionEnding: crate::OnSessionEnding { - /// If there was a validator set change, its returns the set of new validators along with the - /// old validators and their full identifications. + /// If there was a validator set change in the session will_apply_at, its returns the set of + /// new validators along with the old validators and their full identifications. + /// Old validators are the validators for the session just before will_apply_at. fn on_session_ending(ending: SessionIndex, will_apply_at: SessionIndex) -> Option<(Vec, Vec<(ValidatorId, FullIdentification)>)>; } diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index 878acdbce6b3d..d78ee62cabd79 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -25,6 +25,7 @@ pallet-balances = { path = "../balances" } pallet-timestamp = { path = "../timestamp" } pallet-staking-reward-curve = { path = "../staking/reward-curve"} substrate-test-utils = { path = "../../test-utils" } +pretty_assertions = "0.6.1" [features] equalize = [] diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 3d641e24a547d..836705776808a 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -259,7 +259,7 @@ use sp_std::{prelude::*, result}; use codec::{HasCompact, Encode, Decode}; use frame_support::{ decl_module, decl_event, decl_storage, ensure, - weights::SimpleDispatchInfo, + weights::SimpleDispatchInfo, debug::error, traits::{ Currency, OnFreeBalanceZero, LockIdentifier, LockableCurrency, WithdrawReasons, OnUnbalanced, Imbalance, Get, Time @@ -297,7 +297,7 @@ pub type EraIndex = u32; pub type Points = u32; /// Reward points of an era. Used to split era total payout between validators. -#[derive(Encode, Decode, Default)] +#[derive(PartialEq, Encode, Decode, Default, Debug)] pub struct EraPoints { /// Total number of points. Equals the sum of reward points for each validator. total: Points, @@ -308,7 +308,7 @@ pub struct EraPoints { impl EraPoints { /// Add the reward to the validator at the given index. Index must be valid - /// (i.e. `index < current_elected.len()`). + /// (i.e. `index < validator_set.len()`). fn add_points_to_index(&mut self, index: u32, points: u32) { if let Some(new_total) = self.total.checked_add(points) { self.total = new_total; @@ -544,6 +544,14 @@ pub trait SessionInterface: frame_system::Trait { fn prune_historical_up_to(up_to: SessionIndex); } +/// Information stored for current and future era, used for rewarding. +#[derive(PartialEq, Clone, Encode, Decode, RuntimeDebug)] +pub struct ValidatorInfoForEra { + stash: AccountId, + prefs: ValidatorPrefs, + exposure: Exposure, +} + impl SessionInterface<::AccountId> for T where T: pallet_session::Trait::AccountId>, T: pallet_session::historical::Trait< @@ -665,33 +673,36 @@ decl_storage! { /// Direct storage APIs can still bypass this protection. Nominators get(fn nominators): linked_map T::AccountId => Option>; - /// Nominators for a particular account that is in action right now. You can't iterate - /// through validators here, but you can find them in the Session module. - /// - /// This is keyed by the stash account. - pub Stakers get(fn stakers): map T::AccountId => Exposure>; - - /// The currently elected validator set keyed by stash account ID. - pub CurrentElected get(fn current_elected): Vec; - /// The current era index. pub CurrentEra get(fn current_era) config(): EraIndex; /// The start of the current era. pub CurrentEraStart get(fn current_era_start): MomentOf; - /// The session index at which the current era started. - pub CurrentEraStartSessionIndex get(fn current_era_start_session_index): SessionIndex; + /// The session index at which the era started from the current era to the latest planned. + pub EraStartSessionIndex get(fn era_start_session_index) build(|_| vec![0]): Vec; /// Rewards for the current era. Using indices of current elected set. CurrentEraPointsEarned get(fn current_era_reward): EraPoints; + /// Mapping from era to its validator set with all information needed for rewarding. + /// + /// Note: it might contains outdated information if an era planned get unplanned. + pub ValidatorForEra get(fn validator_for_era): map EraIndex + => Vec>>; + /// The amount of balance actively at stake for each validator slot, currently. /// /// This is used to derive rewards and punishments. - pub SlotStake get(fn slot_stake) build(|config: &GenesisConfig| { - config.stakers.iter().map(|&(_, _, value, _)| value).min().unwrap_or_default() - }): BalanceOf; + /// + /// Note: it might contains outdated information if an era planned get unplanned. + pub SlotStakeForEra get(fn slot_stake_for_era) build(|config: &GenesisConfig| { + let initial_slot_stake = config.stakers.iter() + .map(|&(_, _, value, _)| value) + .min() + .unwrap_or_default(); + vec![(0, initial_slot_stake)] + }): map EraIndex => BalanceOf; /// True if the next session change will be a new era regardless of index. pub ForceEra get(fn force_era) config(): Forcing; @@ -733,6 +744,15 @@ decl_storage! { /// The version of storage for upgrade. StorageVersion: u32; + + /// Deprecated: This storage is outdated. Information are not relevant anymore. + Stakers: map T::AccountId => Exposure>; + + /// Deprecated: This storage is outdated. Information are not relevant anymore. + CurrentElected: Vec; + + /// Deprecated: This storage is outdated. Information are not relevant anymore. + SlotStake: BalanceOf; } add_extra_genesis { config(stakers): @@ -1251,14 +1271,18 @@ impl Module { /// Reward a given validator by a specific amount. Add the reward to the validator's, and its /// nominators' balance, pro-rata based on their exposure, after having removed the validator's /// pre-payout cut. - fn reward_validator(stash: &T::AccountId, reward: BalanceOf) -> PositiveImbalanceOf { - let off_the_table = Self::validators(stash).commission * reward; + fn reward_validator( + stash: &T::AccountId, + prefs: &ValidatorPrefs, + exposure: &Exposure>, + reward: BalanceOf, + ) -> PositiveImbalanceOf { + let off_the_table = prefs.commission * reward; let reward = reward.saturating_sub(off_the_table); let mut imbalance = >::zero(); let validator_cut = if reward.is_zero() { Zero::zero() } else { - let exposure = Self::stakers(stash); let total = exposure.total.max(One::one()); for i in &exposure.others { @@ -1275,43 +1299,30 @@ impl Module { imbalance } - /// Session has just ended. Provide the validator set for the next session if it's an era-end, along - /// with the exposure of the prior validator set. - fn new_session(session_index: SessionIndex) - -> Option<(Vec, Vec<(T::AccountId, Exposure>)>)> - { - let era_length = session_index.checked_sub(Self::current_era_start_session_index()).unwrap_or(0); - match ForceEra::get() { - Forcing::ForceNew => ForceEra::kill(), - Forcing::ForceAlways => (), - Forcing::NotForcing if era_length >= T::SessionsPerEra::get() => (), - _ => return None, + /// Call `on_era_end` if session is last of current era. + fn on_session_end(session_index: SessionIndex) { + let era_starts = Self::era_start_session_index(); + if let Some(next_era_start) = era_starts.get(1) { + if *next_era_start == session_index + 1 { + Self::on_era_end(session_index) + } } - let validators = T::SessionInterface::validators(); - let prior = validators.into_iter() - .map(|v| { let e = Self::stakers(&v); (v, e) }) - .collect(); - - Self::new_era(session_index).map(move |new| (new, prior)) } - /// The era has changed - enact new staking set. - /// - /// NOTE: This always happens immediately before a session change to ensure that new validators - /// get a chance to set their session keys. - fn new_era(start_session_index: SessionIndex) -> Option> { - // Payout + /// Reward the era, increment era index, also prune the unecessary information. + fn on_era_end(session_index: SessionIndex) { let points = CurrentEraPointsEarned::take(); let now = T::Time::now(); let previous_era_start = >::mutate(|v| { sp_std::mem::replace(v, now) }); let era_duration = now - previous_era_start; + let current_era = Self::current_era(); if !era_duration.is_zero() { - let validators = Self::current_elected(); + let validators_info = >::take(current_era); - let validator_len: BalanceOf = (validators.len() as u32).into(); - let total_rewarded_stake = Self::slot_stake() * validator_len; + let validator_len: BalanceOf = (validators_info.len() as u32).into(); + let total_rewarded_stake = >::take(current_era) * validator_len; let (total_payout, max_payout) = inflation::compute_total_payout( &T::RewardCurve::get(), @@ -1323,10 +1334,16 @@ impl Module { let mut total_imbalance = >::zero(); - for (v, p) in validators.iter().zip(points.individual.into_iter()) { + for (validator_info, p) in validators_info.iter().zip(points.individual.into_iter()) { if p != 0 { let reward = Perbill::from_rational_approximation(p, points.total) * total_payout; - total_imbalance.subsume(Self::reward_validator(v, reward)); + let imbalance = Self::reward_validator( + &validator_info.stash, + &validator_info.prefs, + &validator_info.exposure, + reward + ); + total_imbalance.subsume(imbalance); } } @@ -1341,15 +1358,15 @@ impl Module { } // Increment current era. - let current_era = CurrentEra::mutate(|s| { *s += 1; *s }); - - CurrentEraStartSessionIndex::mutate(|v| { - *v = start_session_index; + let current_era = current_era + 1; + CurrentEra::put(current_era); + EraStartSessionIndex::mutate(|era_start| { + era_start.remove(0) }); let bonding_duration = T::BondingDuration::get(); BondedEras::mutate(|bonded| { - bonded.push((current_era, start_session_index)); + bonded.push((current_era, session_index + 1)); if current_era > bonding_duration { let first_kept = current_era - bonding_duration; @@ -1370,11 +1387,61 @@ impl Module { } }); - // Reassign all Stakers. - let (_slot_stake, maybe_new_validators) = Self::select_validators(); Self::apply_unapplied_slashes(current_era); + } - maybe_new_validators + /// Provide the validator set for the next future session. If it's an era-end, along with the + /// exposure of the prior validator set. + // Note: if a new future session si planned and trigger a new era due to a ForceNew mecanism, + // this ForceNew mecanism wont trigger necessarily if we plan a new session prior to this + // former planned. + fn new_future_session(will_apply_at: SessionIndex) + -> Option<(Vec, Vec<(T::AccountId, Exposure>)>)> + { + let futurest_era_start = Self::era_start_session_index().last().cloned().unwrap_or(0); + + let era_length = will_apply_at.checked_sub(futurest_era_start).unwrap_or(0); + + match ForceEra::get() { + Forcing::ForceNew => ForceEra::kill(), + Forcing::ForceAlways => (), + Forcing::NotForcing if era_length >= T::SessionsPerEra::get() => (), + _ => return None, + } + + // A future era is planned + let latest_era = EraStartSessionIndex::mutate(|starts| { + // Remove all era planned until this session. + // + // Note: ValidatorForEra and SlotStakeForEra information for such removed eras are + // outdated. + starts.retain(|start| *start < will_apply_at); + + starts.push(will_apply_at); + Self::current_era() + starts.len() as u32 - 1 + }); + + let (new_validators_info, new_slot_stake) = match Self::select_validators() { + Some(set) => set, + None => ( + Self::validator_for_era(latest_era - 1), + Self::slot_stake_for_era(latest_era - 1), + ) + }; + + >::insert(latest_era, &new_validators_info); + >::insert(latest_era, new_slot_stake); + + let prior = Self::validator_for_era(latest_era - 1) + .into_iter() + .map(|validator| (validator.stash, validator.exposure)) + .collect(); + + let new_validators = new_validators_info.into_iter() + .map(|validator| validator.stash) + .collect(); + + Some((new_validators, prior)) } /// Apply previously-unapplied slashes on the beginning of a new era, after a delay. @@ -1398,7 +1465,7 @@ impl Module { /// Returns the new `SlotStake` value and a set of newly selected _stash_ IDs. /// /// Assumes storage is coherent with the declaration. - fn select_validators() -> (BalanceOf, Option>) { + fn select_validators() -> Option<(Vec>>, BalanceOf)> { let mut all_nominators: Vec<(T::AccountId, Vec)> = Vec::new(); let all_validator_candidates_iter = >::enumerate(); let all_validators = all_validator_candidates_iter.map(|(who, _pref)| { @@ -1482,13 +1549,9 @@ impl Module { ); } - // Clear Stakers. - for v in Self::current_elected().iter() { - >::remove(v); - } - // Populate Stakers and figure out the minimum stake behind a slot. let mut slot_stake = BalanceOf::::max_value(); + let mut validators_info = vec![]; for (c, s) in supports.into_iter() { // build `struct exposure` from `support` let exposure = Exposure { @@ -1506,20 +1569,19 @@ impl Module { if exposure.total < slot_stake { slot_stake = exposure.total; } - >::insert(&c, exposure.clone()); - } - - // Update slot stake. - >::put(&slot_stake); - // Set the new validator set in sessions. - >::put(&elected_stashes); + validators_info.push(ValidatorInfoForEra { + prefs: Self::validators(&c), + stash: c, + exposure, + }); + } - // In order to keep the property required by `n_session_ending` + // In order to keep the property required by `on_session_ending` // that we must return the new validator set even if it's the same as the old, // as long as any underlying economic conditions have changed, we don't attempt // to do any optimization where we compare against the prior set. - (slot_stake, Some(elected_stashes)) + Some((validators_info, slot_stake)) } else { // There were not enough candidates for even our minimal level of functionality. // This is bad. @@ -1527,7 +1589,7 @@ impl Module { // and let the chain keep producing blocks until we can decide on a sufficiently // substantial set. // TODO: #2494 - (Self::slot_stake(), None) + None } } @@ -1559,14 +1621,14 @@ impl Module { /// At the end of the era each the total payout will be distributed among validator /// relatively to their points. /// - /// COMPLEXITY: Complexity is `number_of_validator_to_reward x current_elected_len`. + /// COMPLEXITY: Complexity is `number_of_validator_to_reward x validator_set_len`. /// If you need to reward lots of validator consider using `reward_by_indices`. pub fn reward_by_ids(validators_points: impl IntoIterator) { CurrentEraPointsEarned::mutate(|rewards| { - let current_elected = >::current_elected(); - for (validator, points) in validators_points.into_iter() { - if let Some(index) = current_elected.iter() - .position(|elected| *elected == validator) + let session_validators = T::SessionInterface::validators(); + for (rewarded_validator, points) in validators_points.into_iter() { + if let Some(index) = session_validators.iter() + .position(|session_validator| *session_validator == rewarded_validator) { rewards.add_points_to_index(index as u32, points); } @@ -1579,12 +1641,10 @@ impl Module { /// For each element in the iterator the given number of points in u32 is added to the /// validator, thus duplicates are handled. pub fn reward_by_indices(validators_points: impl IntoIterator) { - // TODO: This can be optimised once #3302 is implemented. - let current_elected_len = >::current_elected().len() as u32; - CurrentEraPointsEarned::mutate(|rewards| { + let validator_set_len = T::SessionInterface::validators().len() as u32; for (validator_index, points) in validators_points.into_iter() { - if validator_index < current_elected_len { + if validator_index < validator_set_len { rewards.add_points_to_index(validator_index, points); } } @@ -1601,18 +1661,25 @@ impl Module { } impl pallet_session::OnSessionEnding for Module { - fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex) -> Option> { - Self::ensure_storage_upgraded(); - Self::new_session(start_session - 1).map(|(new, _old)| new) + fn on_session_ending(session_ending: SessionIndex, will_apply_at: SessionIndex) + -> Option> + { + >::on_session_ending(session_ending, will_apply_at) + .map(|(new, _old)| new) } } impl OnSessionEnding>> for Module { - fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex) + fn on_session_ending(session_ending: SessionIndex, will_apply_at: SessionIndex) -> Option<(Vec, Vec<(T::AccountId, Exposure>)>)> { Self::ensure_storage_upgraded(); - Self::new_session(start_session - 1) + + // Must be called before session_ending to register future era + // if `will_apply_at` is `session_ending + 1`. + let future_session = Self::new_future_session(will_apply_at); + Self::on_session_end(session_ending); + future_session } } @@ -1657,13 +1724,31 @@ impl Convert> for ExposureOf { fn convert(validator: T::AccountId) -> Option>> { - Some(>::stakers(&validator)) + let set = >::validator_for_era(>::current_era()); + let exposure = set.into_iter() + .find(|info| info.stash == validator) + .map(|info| info.exposure) + .unwrap_or_else(|| Exposure::default()); + + Some(exposure) } } impl SelectInitialValidators for Module { fn select_initial_validators() -> Option> { - >::select_validators().1 + let (new_validators_info, new_slot_stake) = match Self::select_validators() { + Some(set) => set, + None => { + error!("Staking module: cannot generate initial validator set, \ + potentially due to invalid genesis config"); + + (vec![], 0.into()) + }, + }; + >::insert(Self::current_era(), &new_validators_info); + >::insert(Self::current_era(), new_slot_stake); + + Some(new_validators_info.into_iter().map(|validator_info| validator_info.stash).collect()) } } @@ -1690,7 +1775,8 @@ impl OnOffenceHandler= current_era_start_session { diff --git a/frame/staking/src/migration.rs b/frame/staking/src/migration.rs index 0ee52dc337f9c..aa2e8983b3c8e 100644 --- a/frame/staking/src/migration.rs +++ b/frame/staking/src/migration.rs @@ -20,12 +20,12 @@ pub type VersionNumber = u32; // the current expected version of the storage -pub const CURRENT_VERSION: VersionNumber = 1; +pub const CURRENT_VERSION: VersionNumber = 2; #[cfg(any(test, feature = "migrate"))] mod inner { - use crate::{Store, Module, Trait}; - use frame_support::{StorageLinkedMap, StorageValue}; + use crate::{Store, Module, Trait, ValidatorInfoForEra, SessionInterface}; + use frame_support::{StorageLinkedMap, StorageValue, StorageMap, StoragePrefixedMap}; use sp_std::vec::Vec; use super::{CURRENT_VERSION, VersionNumber}; @@ -60,6 +60,42 @@ mod inner { frame_support::print("Finished migrating Staking storage to v1."); } + // migrate storage from v1 to v2. + // + // * populate `EraStartSessionIndex` storage with only the current era start + // * populate `ValidatorForEra` storage with information from: + // * `Trait::SessionInterface::validators` for validator set + // * `Stakers` for exposure of validators + // * `Validators` for prefs of validators + // * populate `SlotStakeForEra` storage with information from `SlotStake` + // * remove `CurrentEraStartSessionIndex` storage + // * remove `CurrentElected` storage + // * remove `SlotStake` storage + pub fn from_v1_to_v2(version: &mut VersionNumber) { + if *version != 1 { return } + *version += 1; + + let current_era = >::current_era(); + let current_validator_infos = T::SessionInterface::validators().into_iter() + .map(|validator| ValidatorInfoForEra { + prefs: >::validators(&validator), + exposure: as Store>::Stakers::get(&validator), + stash: validator, + }) + .collect::>(); + let current_slot_stake = as Store>::SlotStake::get(); + + as Store>::ValidatorForEra::insert(¤t_era, current_validator_infos); + as Store>::SlotStakeForEra::insert(¤t_era, current_slot_stake); + as Store>::EraStartSessionIndex::put(sp_std::vec![current_era]); + + as Store>::Stakers::remove_all(); + as Store>::CurrentElected::kill(); + as Store>::SlotStake::kill(); + + frame_support::print("Finished migrating Staking storage to v2."); + } + pub(super) fn perform_migrations() { as Store>::StorageVersion::mutate(|version| { if *version < MIN_SUPPORTED_VERSION { @@ -72,6 +108,7 @@ mod inner { if *version == CURRENT_VERSION { return } to_v1::(version); + from_v1_to_v2::(version); }); } } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 16c587f9bec10..4b825d93153a7 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -25,13 +25,13 @@ use sp_staking::{SessionIndex, offence::{OffenceDetails, OnOffenceHandler}}; use sp_core::{H256, crypto::key_types}; use sp_io; use frame_support::{ - assert_ok, impl_outer_origin, parameter_types, StorageLinkedMap, StorageValue, + assert_ok, impl_outer_origin, parameter_types, StorageLinkedMap, StorageValue, StorageMap, traits::{Currency, Get, FindAuthor}, weights::Weight, }; use crate::{ EraIndex, GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination, - Nominators, inflation + Nominators, inflation, ValidatorInfoForEra, SessionInterface, ValidatorForEra, Exposure, }; /// The AccountId alias in this test module. @@ -372,18 +372,21 @@ pub type Session = pallet_session::Module; pub type Timestamp = pallet_timestamp::Module; pub type Staking = Module; -pub fn check_exposure_all() { - Staking::current_elected().into_iter().for_each(|acc| check_exposure(acc)); +pub fn check_exposure_all(era: EraIndex) { + Staking::validator_for_era(era).into_iter().for_each(|info| check_exposure(info)); } -pub fn check_nominator_all() { - >::enumerate().for_each(|(acc, _)| check_nominator_exposure(acc)); +pub fn check_nominator_all(era: EraIndex) { + >::enumerate() + .for_each(|(acc, _)| check_nominator_exposure(era, acc)); } /// Check for each selected validator: expo.total = Sum(expo.other) + expo.own -pub fn check_exposure(stash: u64) { +pub fn check_exposure(val_info: ValidatorInfoForEra) { + let stash = val_info.stash; + let expo = val_info.exposure; + assert_is_stash(stash); - let expo = Staking::stakers(&stash); assert_eq!( expo.total as u128, expo.own as u128 + expo.others.iter().map(|e| e.value as u128).sum::(), "wrong total exposure for {:?}: {:?}", stash, expo, @@ -392,15 +395,16 @@ pub fn check_exposure(stash: u64) { /// Check that for each nominator: slashable_balance > sum(used_balance) /// Note: we might not consume all of a nominator's balance, but we MUST NOT over spend it. -pub fn check_nominator_exposure(stash: u64) { +pub fn check_nominator_exposure(era: EraIndex, stash: AccountId) { assert_is_stash(stash); let mut sum = 0; - Staking::current_elected() + Staking::validator_for_era(era) .iter() - .map(|v| Staking::stakers(v)) - .for_each(|e| e.others.iter() - .filter(|i| i.who == stash) - .for_each(|i| sum += i.value)); + .for_each(|e| { + e.exposure.others.iter() + .filter(|i| i.who == stash) + .for_each(|i| sum += i.value) + }); let nominator_stake = Staking::slashable_balance_of(&stash); // a nominator cannot over-spend. assert!( @@ -409,11 +413,11 @@ pub fn check_nominator_exposure(stash: u64) { ); } -pub fn assert_is_stash(acc: u64) { +pub fn assert_is_stash(acc: AccountId) { assert!(Staking::bonded(&acc).is_some(), "Not a stash."); } -pub fn assert_ledger_consistent(stash: u64) { +pub fn assert_ledger_consistent(stash: AccountId) { assert_is_stash(stash); let ledger = Staking::ledger(stash - 1).unwrap(); @@ -443,8 +447,6 @@ pub fn advance_session() { } pub fn start_session(session_index: SessionIndex) { - // Compensate for session delay - let session_index = session_index + 1; for i in Session::current_index()..session_index { System::set_block_number((i + 1).into()); Timestamp::set_timestamp(System::block_number() * 1000); @@ -462,16 +464,15 @@ pub fn start_era(era_index: EraIndex) { pub fn current_total_payout_for_duration(duration: u64) -> u64 { inflation::compute_total_payout( ::RewardCurve::get(), - >::slot_stake() * 2, + Staking::slot_stake_for_era(Staking::current_era()) * 2, Balances::total_issuance(), duration, ).0 } pub fn reward_all_elected() { - let rewards = >::current_elected().iter() - .map(|v| (*v, 1)) - .collect::>(); + let rewards = ::SessionInterface::validators().into_iter() + .map(|v| (v, 1)); >::reward_by_ids(rewards) } @@ -496,7 +497,7 @@ pub fn on_offence_in_era( } if Staking::current_era() == era { - Staking::on_offence(offenders, slash_fraction, Staking::current_era_start_session_index()); + Staking::on_offence(offenders, slash_fraction, Staking::era_start_session_index()[0]); } else { panic!("cannot slash in era {}", era); } @@ -509,3 +510,20 @@ pub fn on_offence_now( let now = Staking::current_era(); on_offence_in_era(offenders, slash_fraction, now) } + +pub fn validator_current_info(stash: AccountId) -> ValidatorInfoForEra { + Staking::validator_for_era(Staking::current_era()) + .into_iter() + .find(|info| info.stash == stash) + .unwrap() +} + +pub fn bypass_logic_change_current_exposure(stash: AccountId, exposure: Exposure) { + >::mutate(Staking::current_era(), |infos| { + for info in infos { + if info.stash == stash { + info.exposure = exposure.clone(); + } + } + }); +} diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index c31cdf7611234..c8ac5a7b90a22 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -84,26 +84,56 @@ fn basic_setup_works() { if cfg!(feature = "equalize") { assert_eq!( - Staking::stakers(11), - Exposure { total: 1250, own: 1000, others: vec![ IndividualExposure { who: 101, value: 250 }] } - ); - assert_eq!( - Staking::stakers(21), - Exposure { total: 1250, own: 1000, others: vec![ IndividualExposure { who: 101, value: 250 }] } + Staking::validator_for_era(Staking::current_era()), + vec![ + ValidatorInfoForEra { + stash: 11, + prefs: ValidatorPrefs::default(), + exposure: Exposure { + total: 1250, + own: 1000, + others: vec![ IndividualExposure { who: 101, value: 250 }] + }, + }, + ValidatorInfoForEra { + stash: 21, + prefs: ValidatorPrefs::default(), + exposure: Exposure { + total: 1250, + own: 1000, + others: vec![ IndividualExposure { who: 101, value: 250 }] + }, + }, + ] ); // initial slot_stake - assert_eq!(Staking::slot_stake(), 1250); + assert_eq!(Staking::slot_stake_for_era(Staking::current_era()), 1250); } else { assert_eq!( - Staking::stakers(11), - Exposure { total: 1125, own: 1000, others: vec![ IndividualExposure { who: 101, value: 125 }] } - ); - assert_eq!( - Staking::stakers(21), - Exposure { total: 1375, own: 1000, others: vec![ IndividualExposure { who: 101, value: 375 }] } + Staking::validator_for_era(Staking::current_era()), + vec![ + ValidatorInfoForEra { + stash: 11, + prefs: ValidatorPrefs::default(), + exposure: Exposure { + total: 1125, + own: 1000, + others: vec![ IndividualExposure { who: 101, value: 125 }] + }, + }, + ValidatorInfoForEra { + stash: 21, + prefs: ValidatorPrefs::default(), + exposure: Exposure { + total: 1375, + own: 1000, + others: vec![ IndividualExposure { who: 101, value: 375 }] + }, + }, + ] ); // initial slot_stake - assert_eq!(Staking::slot_stake(), 1125); + assert_eq!(Staking::slot_stake_for_era(Staking::current_era()), 1125); } @@ -121,8 +151,8 @@ fn basic_setup_works() { assert_eq!(Staking::force_era(), Forcing::NotForcing); // All exposures must be correct. - check_exposure_all(); - check_nominator_all(); + check_exposure_all(Staking::current_era()); + check_nominator_all(Staking::current_era()); }); } @@ -131,10 +161,10 @@ fn change_controller_works() { ExtBuilder::default().build().execute_with(|| { assert_eq!(Staking::bonded(&11), Some(10)); - assert!(>::enumerate().map(|(c, _)| c).collect::>().contains(&11)); + assert!(Session::validators().contains(&11)); // 10 can control 11 who is initially a validator. assert_ok!(Staking::chill(Origin::signed(10))); - assert!(!>::enumerate().map(|(c, _)| c).collect::>().contains(&11)); + assert!(Session::validators().contains(&11)); assert_ok!(Staking::set_controller(Origin::signed(11), 5)); @@ -149,47 +179,25 @@ fn change_controller_works() { } #[test] +#[cfg(feature = "equalize")] fn rewards_should_work() { // should check that: // * rewards get recorded per session // * rewards get paid per Era // * Check that nominators are also rewarded - ExtBuilder::default().nominate(false).build().execute_with(|| { - // Init some balances - let _ = Balances::make_free_balance_be(&2, 500); - - let delay = 1000; - let init_balance_2 = Balances::total_balance(&2); + ExtBuilder::default().nominate(true).build().execute_with(|| { let init_balance_10 = Balances::total_balance(&10); let init_balance_11 = Balances::total_balance(&11); + let init_balance_20 = Balances::total_balance(&20); + let init_balance_21 = Balances::total_balance(&21); + let init_balance_100 = Balances::total_balance(&100); + let init_balance_101 = Balances::total_balance(&101); - // Set payee to controller - assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); - - // Initial config should be correct - assert_eq!(Staking::current_era(), 0); - assert_eq!(Session::current_index(), 0); - - // Add a dummy nominator. - // - // Equal division indicates that the reward will be equally divided among validator and - // nominator. - >::insert(&11, Exposure { - own: 500, - total: 1000, - others: vec![IndividualExposure {who: 2, value: 500 }] - }); - - >::insert(&2, RewardDestination::Stash); - assert_eq!(Staking::payee(2), RewardDestination::Stash); - assert_eq!(Staking::payee(11), RewardDestination::Controller); + // Check state + Payee::::insert(11, RewardDestination::Controller); + Payee::::insert(21, RewardDestination::Controller); + Payee::::insert(101, RewardDestination::Controller); - let mut block = 3; // Block 3 => Session 1 => Era 0 - System::set_block_number(block); - Timestamp::set_timestamp(block * 5000); // on time. - Session::on_initialize(System::block_number()); - assert_eq!(Staking::current_era(), 0); - assert_eq!(Session::current_index(), 1); >::reward_by_ids(vec![(11, 50)]); >::reward_by_ids(vec![(11, 50)]); // This is the second validator of the current elected set. @@ -198,70 +206,50 @@ fn rewards_should_work() { >::reward_by_ids(vec![(1001, 10_000)]); // Compute total payout now for whole duration as other parameter won't change - let total_payout = current_total_payout_for_duration(9 * 5 * 1000); - assert!(total_payout > 10); // Test is meaningful if reward something - - // No reward yet - assert_eq!(Balances::total_balance(&2), init_balance_2); - assert_eq!(Balances::total_balance(&10), init_balance_10); - assert_eq!(Balances::total_balance(&11), init_balance_11); - - block = 6; // Block 6 => Session 2 => Era 0 - System::set_block_number(block); - Timestamp::set_timestamp(block * 5000 + delay); // a little late. - Session::on_initialize(System::block_number()); - assert_eq!(Staking::current_era(), 0); - assert_eq!(Session::current_index(), 2); + let total_payout_0 = current_total_payout_for_duration(3 * 1000); + assert!(total_payout_0 > 10); // Test is meaningful if reward something - block = 9; // Block 9 => Session 3 => Era 1 - System::set_block_number(block); - Timestamp::set_timestamp(block * 5000); // back to being on time. no delays - Session::on_initialize(System::block_number()); - assert_eq!(Staking::current_era(), 1); - assert_eq!(Session::current_index(), 3); + start_session(1); - // 11 validator has 2/3 of the total rewards and half half for it and its nominator - assert_eq_error_rate!(Balances::total_balance(&2), init_balance_2 + total_payout / 3, 1); - assert_eq_error_rate!(Balances::total_balance(&10), init_balance_10 + total_payout / 3, 1); + assert_eq!(Balances::total_balance(&10), init_balance_10); assert_eq!(Balances::total_balance(&11), init_balance_11); - }); -} - -#[test] -fn multi_era_reward_should_work() { - // Should check that: - // The value of current_session_reward is set at the end of each era, based on - // slot_stake and session_reward. - ExtBuilder::default().nominate(false).build().execute_with(|| { - let init_balance_10 = Balances::total_balance(&10); - - // Set payee to controller - assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); - - // Compute now as other parameter won't change - let total_payout_0 = current_total_payout_for_duration(3000); - assert!(total_payout_0 > 10); // Test is meaningfull if reward something - >::reward_by_ids(vec![(11, 1)]); + assert_eq!(Balances::total_balance(&20), init_balance_20); + assert_eq!(Balances::total_balance(&21), init_balance_21); + assert_eq!(Balances::total_balance(&100), init_balance_100); + assert_eq!(Balances::total_balance(&101), init_balance_101); + assert_eq!(Session::validators(), vec![11, 21]); + assert_eq!(Staking::current_era_reward(), EraPoints { + total: 50*3, + individual: vec![100, 50], + }); - start_session(0); - start_session(1); start_session(2); start_session(3); assert_eq!(Staking::current_era(), 1); - assert_eq!(Balances::total_balance(&10), init_balance_10 + total_payout_0); - start_session(4); + assert_eq_error_rate!(Balances::total_balance(&10), init_balance_10 + total_payout_0*2/3*1000/1250, 2); + assert_eq_error_rate!(Balances::total_balance(&11), init_balance_11, 2); + assert_eq_error_rate!(Balances::total_balance(&20), init_balance_20 + total_payout_0*1/3*1000/1250, 2); + assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); + assert_eq_error_rate!(Balances::total_balance(&100), init_balance_100 + total_payout_0*250/1250, 2); + assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); - let total_payout_1 = current_total_payout_for_duration(3000); - assert!(total_payout_1 > 10); // Test is meaningfull if reward something - >::reward_by_ids(vec![(11, 101)]); + assert_eq!(Session::validators(), vec![11, 21]); + >::reward_by_indices(vec![(0, 1)]); - // new era is triggered here. - start_session(5); + // Compute total payout now for whole duration as other parameter won't change + let total_payout_1 = current_total_payout_for_duration(3 * 1000); + assert!(total_payout_1 > 10); // Test is meaningful if reward something + + start_era(2); - // pay time - assert_eq!(Balances::total_balance(&10), init_balance_10 + total_payout_0 + total_payout_1); + assert_eq_error_rate!(Balances::total_balance(&10), init_balance_10 + (total_payout_0*2/3 + total_payout_1)*1000/1250, 2); + assert_eq_error_rate!(Balances::total_balance(&11), init_balance_11, 2); + assert_eq_error_rate!(Balances::total_balance(&20), init_balance_20 + total_payout_0*1/3*1000/1250, 2); + assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); + assert_eq_error_rate!(Balances::total_balance(&100), init_balance_100 + (total_payout_0 + total_payout_1)*250/1250, 2); + assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); }); } @@ -276,6 +264,9 @@ fn staking_should_work() { .fair(false) // to give 20 more staked value .build() .execute_with(|| { + // --- Block 1: + start_session(1); + Timestamp::set_timestamp(1); // Initialize time. // remember + compare this along with the test. @@ -284,8 +275,8 @@ fn staking_should_work() { // put some money in account that we'll use. for i in 1..5 { let _ = Balances::make_free_balance_be(&i, 2000); } - // --- Block 1: - start_session(1); + // --- Block 2: + start_session(2); // add a new candidate for being a validator. account 3 controlled by 4. assert_ok!(Staking::bond(Origin::signed(3), 4, 1500, RewardDestination::Controller)); assert_ok!(Staking::validate(Origin::signed(4), ValidatorPrefs::default())); @@ -293,31 +284,37 @@ fn staking_should_work() { // No effects will be seen so far. assert_eq_uvec!(validator_controllers(), vec![20, 10]); - // --- Block 2: - start_session(2); + // --- Block 3: + start_session(3); // No effects will be seen so far. Era has not been yet triggered. assert_eq_uvec!(validator_controllers(), vec![20, 10]); - // --- Block 3: the validators will now be queued. - start_session(3); + // --- Block 4: the validators will now be queued. + start_session(4); assert_eq!(Staking::current_era(), 1); - // --- Block 4: the validators will now be changed. - start_session(4); + // --- Block 5: the validators are still in queue. + start_session(5); + + // --- Block 6: the validators will now be changed. + start_session(6); assert_eq_uvec!(validator_controllers(), vec![20, 4]); - // --- Block 4: Unstake 4 as a validator, freeing up the balance stashed in 3 + // --- Block 6: Unstake 4 as a validator, freeing up the balance stashed in 3 // 4 will chill Staking::chill(Origin::signed(4)).unwrap(); - // --- Block 5: nothing. 4 is still there. - start_session(5); + // --- Block 7: nothing. 4 is still there. + start_session(7); assert_eq_uvec!(validator_controllers(), vec![20, 4]); - // --- Block 6: 4 will not be a validator. - start_session(7); + // --- Block 8: + start_session(8); + + // --- Block 9: 4 will not be a validator. + start_session(9); assert_eq_uvec!(validator_controllers(), vec![20, 10]); // Note: the stashed value of 4 is still lock @@ -351,18 +348,19 @@ fn less_than_needed_candidates_works() { // But the exposure is updated in a simple way. No external votes exists. // This is purely self-vote. - assert_eq!(Staking::stakers(10).others.len(), 0); - assert_eq!(Staking::stakers(20).others.len(), 0); - assert_eq!(Staking::stakers(30).others.len(), 0); - check_exposure_all(); - check_nominator_all(); + assert!( + Staking::validator_for_era(Staking::current_era()).iter() + .all(|info| info.exposure.others.is_empty()) + ); + check_exposure_all(Staking::current_era()); + check_nominator_all(Staking::current_era()); }); } #[test] fn no_candidate_emergency_condition() { ExtBuilder::default() - .minimum_validator_count(10) + .minimum_validator_count(4) .validator_count(15) .num_validators(4) .validator_pool(true) @@ -371,21 +369,17 @@ fn no_candidate_emergency_condition() { .execute_with(|| { // initial validators assert_eq_uvec!(validator_controllers(), vec![10, 20, 30, 40]); - - // set the minimum validator count. - ::MinimumValidatorCount::put(10); - ::ValidatorCount::put(15); assert_eq!(Staking::validator_count(), 15); + // try to chill let _ = Staking::chill(Origin::signed(10)); // trigger era - System::set_block_number(1); - Session::on_initialize(System::block_number()); + start_era(1); // Previous ones are elected. chill is invalidates. TODO: #2494 assert_eq_uvec!(validator_controllers(), vec![10, 20, 30, 40]); - assert_eq!(Staking::current_elected().len(), 0); + assert_eq!(Session::validators(), vec![11, 21, 31, 41]); }); } @@ -474,61 +468,69 @@ fn nominating_and_rewards_should_work() { // ------ check the staked value of all parties. if cfg!(feature = "equalize") { - // total expo of 10, with 1200 coming from nominators (externals), according to phragmen. - assert_eq!(Staking::stakers(11).own, 1000); - assert_eq_error_rate!(Staking::stakers(11).total, 1000 + 1000, 2); - // 2 and 4 supported 10, each with stake 600, according to phragmen. - assert_eq!( - Staking::stakers(11).others.iter().map(|e| e.value).collect::>>(), - vec![600, 400] - ); - assert_eq!( - Staking::stakers(11).others.iter().map(|e| e.who).collect::>(), - vec![3, 1] - ); - // total expo of 20, with 500 coming from nominators (externals), according to phragmen. - assert_eq!(Staking::stakers(21).own, 1000); - assert_eq_error_rate!(Staking::stakers(21).total, 1000 + 1000, 2); - // 2 and 4 supported 20, each with stake 250, according to phragmen. - assert_eq!( - Staking::stakers(21).others.iter().map(|e| e.value).collect::>>(), - vec![400, 600] - ); - assert_eq!( - Staking::stakers(21).others.iter().map(|e| e.who).collect::>(), - vec![3, 1] + pretty_assertions::assert_eq!( + Staking::validator_for_era(Staking::current_era()), + // 30 and 40 are not chosen anymore + vec![ + ValidatorInfoForEra { + stash: 11, + prefs: ValidatorPrefs::default(), + exposure: Exposure { + total: 1000 + 1000, + own: 1000, + others: vec![ + IndividualExposure { who: 3, value: 600 }, + IndividualExposure { who: 1, value: 400 }, + ] + }, + }, + ValidatorInfoForEra { + stash: 21, + prefs: ValidatorPrefs::default(), + exposure: Exposure { + total: 1000 + 1000, + own: 1000, + others: vec![ + IndividualExposure { who: 3, value: 400 }, + IndividualExposure { who: 1, value: 600 }, + ] + }, + }, + ] ); } else { - // total expo of 10, with 1200 coming from nominators (externals), according to phragmen. - assert_eq!(Staking::stakers(11).own, 1000); - assert_eq!(Staking::stakers(11).total, 1000 + 800); - // 2 and 4 supported 10, each with stake 600, according to phragmen. - assert_eq!( - Staking::stakers(11).others.iter().map(|e| e.value).collect::>>(), - vec![400, 400] - ); - assert_eq!( - Staking::stakers(11).others.iter().map(|e| e.who).collect::>(), - vec![3, 1] - ); - // total expo of 20, with 500 coming from nominators (externals), according to phragmen. - assert_eq!(Staking::stakers(21).own, 1000); - assert_eq_error_rate!(Staking::stakers(21).total, 1000 + 1200, 2); - // 2 and 4 supported 20, each with stake 250, according to phragmen. - assert_eq!( - Staking::stakers(21).others.iter().map(|e| e.value).collect::>>(), - vec![600, 600] - ); - assert_eq!( - Staking::stakers(21).others.iter().map(|e| e.who).collect::>(), - vec![3, 1] + pretty_assertions::assert_eq!( + Staking::validator_for_era(Staking::current_era()), + // 30 and 40 are not chosen anymore + vec![ + ValidatorInfoForEra { + stash: 11, + prefs: ValidatorPrefs::default(), + exposure: Exposure { + total: 1000 + 800, + own: 1000, + others: vec![ + IndividualExposure { who: 3, value: 400 }, + IndividualExposure { who: 1, value: 400 }, + ] + }, + }, + ValidatorInfoForEra { + stash: 21, + prefs: ValidatorPrefs::default(), + exposure: Exposure { + total: 1000 + 1200, + own: 1000, + others: vec![ + IndividualExposure { who: 3, value: 600 }, + IndividualExposure { who: 1, value: 600 }, + ] + }, + }, + ] ); } - // They are not chosen anymore - assert_eq!(Staking::stakers(31).total, 0); - assert_eq!(Staking::stakers(41).total, 0); - // the total reward for era 1 let total_payout_1 = current_total_payout_for_duration(3000); assert!(total_payout_1 > 100); // Test is meaningfull if reward something @@ -598,8 +600,8 @@ fn nominating_and_rewards_should_work() { ); } - check_exposure_all(); - check_nominator_all(); + check_exposure_all(Staking::current_era()); + check_nominator_all(Staking::current_era()); }); } @@ -642,13 +644,13 @@ fn nominators_also_get_slashed() { &[OffenceDetails { offender: ( 11, - Staking::stakers(&11), + mock::validator_current_info(11).exposure, ), reporters: vec![], }], &[Perbill::from_percent(5)], ); - let expo = Staking::stakers(11); + let expo = mock::validator_current_info(11).exposure; let slash_value = 50; let total_slash = expo.total.min(slash_value); let validator_slash = expo.own.min(total_slash); @@ -657,8 +659,8 @@ fn nominators_also_get_slashed() { // initial + first era reward + slash assert_eq!(Balances::total_balance(&11), initial_balance - validator_slash); assert_eq!(Balances::total_balance(&2), initial_balance - nominator_slash); - check_exposure_all(); - check_nominator_all(); + check_exposure_all(Staking::current_era()); + check_nominator_all(Staking::current_era()); // Because slashing happened. assert!(is_disabled(10)); }); @@ -716,37 +718,47 @@ fn session_and_eras_work() { assert_eq!(Staking::current_era(), 0); // Block 1: No change. - start_session(0); + start_session(1); assert_eq!(Session::current_index(), 1); assert_eq!(Staking::current_era(), 0); - // Block 2: Simple era change. + // Block 2: No change. start_session(2); + assert_eq!(Session::current_index(), 2); + assert_eq!(Staking::current_era(), 0); + + // Block 3: Era increment. + start_session(3); assert_eq!(Session::current_index(), 3); assert_eq!(Staking::current_era(), 1); - // Block 3: Schedule an era length change; no visible changes. - start_session(3); + // Block 4: No change. + start_session(4); assert_eq!(Session::current_index(), 4); assert_eq!(Staking::current_era(), 1); - // Block 4: Era change kicks in. + // Block 5: No change. start_session(5); + assert_eq!(Session::current_index(), 5); + assert_eq!(Staking::current_era(), 1); + + // Block 6: Era increment. + start_session(6); assert_eq!(Session::current_index(), 6); assert_eq!(Staking::current_era(), 2); - // Block 5: No change. - start_session(6); + // Block 7: No change. + start_session(7); assert_eq!(Session::current_index(), 7); assert_eq!(Staking::current_era(), 2); - // Block 6: No change. - start_session(7); + // Block 8: No change. + start_session(8); assert_eq!(Session::current_index(), 8); assert_eq!(Staking::current_era(), 2); - // Block 7: Era increment. - start_session(8); + // Block 9: Era increment. + start_session(9); assert_eq!(Session::current_index(), 9); assert_eq!(Staking::current_era(), 3); }); @@ -762,43 +774,46 @@ fn forcing_new_era_works() { start_session(1); assert_eq!(Staking::current_era(), 0); start_session(2); + assert_eq!(Staking::current_era(), 0); + start_session(3); assert_eq!(Staking::current_era(), 1); // no era change. ForceEra::put(Forcing::ForceNone); - start_session(3); - assert_eq!(Staking::current_era(), 1); start_session(4); assert_eq!(Staking::current_era(), 1); start_session(5); assert_eq!(Staking::current_era(), 1); start_session(6); assert_eq!(Staking::current_era(), 1); + start_session(7); + assert_eq!(Staking::current_era(), 1); // back to normal. // this immediately starts a new session. ForceEra::put(Forcing::NotForcing); - start_session(7); - assert_eq!(Staking::current_era(), 2); start_session(8); + assert_eq!(Staking::current_era(), 1); // There is one session delay + start_session(9); assert_eq!(Staking::current_era(), 2); // forceful change ForceEra::put(Forcing::ForceAlways); - start_session(9); - assert_eq!(Staking::current_era(), 3); start_session(10); - assert_eq!(Staking::current_era(), 4); + assert_eq!(Staking::current_era(), 2); // There is one session delay start_session(11); - assert_eq!(Staking::current_era(), 5); + assert_eq!(Staking::current_era(), 3); + start_session(12); + assert_eq!(Staking::current_era(), 4); // just one forceful change ForceEra::put(Forcing::ForceNew); - start_session(12); - assert_eq!(Staking::current_era(), 6); - - assert_eq!(ForceEra::get(), Forcing::NotForcing); start_session(13); + assert_eq!(Staking::current_era(), 5); + assert_eq!(ForceEra::get(), Forcing::NotForcing); + start_session(14); + assert_eq!(Staking::current_era(), 6); + start_session(15); assert_eq!(Staking::current_era(), 6); }); @@ -813,7 +828,7 @@ fn cannot_transfer_staked_balance() { // Confirm account 11 has some free balance assert_eq!(Balances::free_balance(&11), 1000); // Confirm account 11 (via controller 10) is totally staked - assert_eq!(Staking::stakers(&11).total, 1000); + assert_eq!(mock::validator_current_info(11).exposure.total, 1000); // Confirm account 11 cannot transfer as a result assert_noop!( Balances::transfer(Origin::signed(11), 20, 1), @@ -838,7 +853,7 @@ fn cannot_transfer_staked_balance_2() { // Confirm account 21 has some free balance assert_eq!(Balances::free_balance(&21), 2000); // Confirm account 21 (via controller 20) is totally staked - assert_eq!(Staking::stakers(&21).total, 1000); + assert_eq!(mock::validator_current_info(21).exposure.total, 1000); // Confirm account 21 can transfer at most 1000 assert_noop!( Balances::transfer(Origin::signed(21), 20, 1001), @@ -857,7 +872,7 @@ fn cannot_reserve_staked_balance() { // Confirm account 11 has some free balance assert_eq!(Balances::free_balance(&11), 1000); // Confirm account 11 (via controller 10) is totally staked - assert_eq!(Staking::stakers(&11).own, 1000); + assert_eq!(mock::validator_current_info(11).exposure.own, 1000); // Confirm account 11 cannot transfer as a result assert_noop!(Balances::reserve(&11, 1), "account liquidity restrictions prevent withdrawal"); @@ -873,7 +888,7 @@ fn reward_destination_works() { // Rewards go to the correct destination as determined in Payee ExtBuilder::default().nominate(false).build().execute_with(|| { // Check that account 11 is a validator - assert!(Staking::current_elected().contains(&11)); + assert!(Session::validators().contains(&11)); // Check the balance of the validator account assert_eq!(Balances::free_balance(&10), 1); // Check the balance of the stash account @@ -964,45 +979,37 @@ fn validator_payment_prefs_work() { // Note: unstake threshold is being directly tested in slashing tests. // This test will focus on validator payment. ExtBuilder::default().build().execute_with(|| { - // Initial config - let stash_initial_balance = Balances::total_balance(&11); - - // check the balance of a validator accounts. - assert_eq!(Balances::total_balance(&10), 1); - // check the balance of a validator's stash accounts. - assert_eq!(Balances::total_balance(&11), stash_initial_balance); - // and the nominator (to-be) - let _ = Balances::make_free_balance_be(&2, 500); - - // add a dummy nominator. - >::insert(&11, Exposure { - own: 500, // equal division indicates that the reward will be equally divided among validator and nominator. - total: 1000, - others: vec![IndividualExposure {who: 2, value: 500 }] - }); - >::insert(&2, RewardDestination::Stash); + let commission = Perbill::from_percent(40); >::insert(&11, ValidatorPrefs { - commission: Perbill::from_percent(50), + commission: commission.clone(), }); + // Reward controller so staked ratio doesn't change. + >::insert(&11, RewardDestination::Controller); + >::insert(&101, RewardDestination::Controller); + + start_era(1); + + let balance_era_1_10 = Balances::total_balance(&10); + let balance_era_1_100 = Balances::total_balance(&100); + // Compute total payout now for whole duration as other parameter won't change - let total_payout_0 = current_total_payout_for_duration(3000); - assert!(total_payout_0 > 100); // Test is meaningfull if reward something + let total_payout_1 = current_total_payout_for_duration(3000); + let exposure_1 = mock::validator_current_info(11).exposure; + assert!(total_payout_1 > 100); // Test is meaningfull if reward something >::reward_by_ids(vec![(11, 1)]); - start_era(1); + start_era(2); - // whats left to be shared is the sum of 3 rounds minus the validator's cut. - let shared_cut = total_payout_0 / 2; - // Validator's payee is Staked account, 11, reward will be paid here. - assert_eq!(Balances::total_balance(&11), stash_initial_balance + shared_cut / 2 + shared_cut); - // Controller account will not get any reward. - assert_eq!(Balances::total_balance(&10), 1); - // Rest of the reward will be shared and paid to the nominator in stake. - assert_eq!(Balances::total_balance(&2), 500 + shared_cut / 2); + let taken_cut = commission * total_payout_1; + let shared_cut = total_payout_1 - taken_cut; + let reward_of_10 = shared_cut * exposure_1.own / exposure_1.total + taken_cut; + let reward_of_100 = shared_cut * exposure_1.others[0].value / exposure_1.total; + assert_eq_error_rate!(Balances::total_balance(&10), balance_era_1_10 + reward_of_10, 2); + assert_eq_error_rate!(Balances::total_balance(&100), balance_era_1_100 + reward_of_100, 2); - check_exposure_all(); - check_nominator_all(); + check_exposure_all(Staking::current_era()); + check_nominator_all(Staking::current_era()); }); } @@ -1014,7 +1021,7 @@ fn bond_extra_works() { // See `bond_extra_and_withdraw_unbonded_works` for more details and updates on `Exposure`. ExtBuilder::default().build().execute_with(|| { // Check that account 10 is a validator - assert!(>::exists(11)); + assert!(Session::validators().contains(&11)); // Check that account 10 is bonded to account 11 assert_eq!(Staking::bonded(&11), Some(10)); // Check how much is at stake @@ -1081,7 +1088,7 @@ fn bond_extra_and_withdraw_unbonded_works() { active: 1000, unlocking: vec![], })); - assert_eq!(Staking::stakers(&11), Exposure { total: 1000, own: 1000, others: vec![] }); + assert_eq!(mock::validator_current_info(11).exposure, Exposure { total: 1000, own: 1000, others: vec![] }); // deposit the extra 100 units Staking::bond_extra(Origin::signed(11), 100).unwrap(); @@ -1093,7 +1100,7 @@ fn bond_extra_and_withdraw_unbonded_works() { unlocking: vec![], })); // Exposure is a snapshot! only updated after the next era update. - assert_ne!(Staking::stakers(&11), Exposure { total: 1000 + 100, own: 1000 + 100, others: vec![] }); + assert_ne!(mock::validator_current_info(11).exposure, Exposure { total: 1000 + 100, own: 1000 + 100, others: vec![] }); // trigger next era. Timestamp::set_timestamp(10); @@ -1108,7 +1115,7 @@ fn bond_extra_and_withdraw_unbonded_works() { unlocking: vec![], })); // Exposure is now updated. - assert_eq!(Staking::stakers(&11), Exposure { total: 1000 + 100, own: 1000 + 100, others: vec![] }); + assert_eq!(mock::validator_current_info(11).exposure, Exposure { total: 1000 + 100, own: 1000 + 100, others: vec![] }); // Unbond almost all of the funds in stash. Staking::unbond(Origin::signed(10), 1000).unwrap(); @@ -1174,21 +1181,21 @@ fn slot_stake_is_least_staked_validator_and_exposure_defines_maximum_punishment( // Confirm validator count is 2 assert_eq!(Staking::validator_count(), 2); // Confirm account 10 and 20 are validators - assert!(>::exists(&11) && >::exists(&21)); + assert_eq_uvec!(Session::validators(), vec![11, 21]); - assert_eq!(Staking::stakers(&11).total, 1000); - assert_eq!(Staking::stakers(&21).total, 2000); + assert_eq!(mock::validator_current_info(11).exposure.total, 1000); + assert_eq!(mock::validator_current_info(21).exposure.total, 2000); // Give the man some money. let _ = Balances::make_free_balance_be(&10, 1000); let _ = Balances::make_free_balance_be(&20, 1000); // We confirm initialized slot_stake is this value - assert_eq!(Staking::slot_stake(), Staking::stakers(&11).total); + assert_eq!(Staking::slot_stake_for_era(Staking::current_era()), mock::validator_current_info(11).exposure.total); // Now lets lower account 20 stake - >::insert(&21, Exposure { total: 69, own: 69, others: vec![] }); - assert_eq!(Staking::stakers(&21).total, 69); + mock::bypass_logic_change_current_exposure(21, Exposure { total: 69, own: 69, others: vec![] }); + assert_eq!(mock::validator_current_info(21).exposure.total, 69); >::insert(&20, StakingLedger { stash: 22, total: 69, active: 69, unlocking: vec![] }); // Compute total payout now for whole duration as other parameter won't change @@ -1199,19 +1206,24 @@ fn slot_stake_is_least_staked_validator_and_exposure_defines_maximum_punishment( // New era --> rewards are paid --> stakes are changed start_era(1); - - // -- new balances + reward - assert_eq!(Staking::stakers(&11).total, 1000 + total_payout_0 / 2); - assert_eq!(Staking::stakers(&21).total, 69 + total_payout_0 / 2); + assert_eq!(mock::validator_current_info(11).exposure.total, 1000); + assert_eq!(mock::validator_current_info(21).exposure.total, 69); + assert_eq!(Staking::slot_stake_for_era(Staking::current_era()), 69); let _11_balance = Balances::free_balance(&11); assert_eq!(_11_balance, 1000 + total_payout_0 / 2); + // Trigger another new era as the info are frozen before the era start. + start_era(2); + + // -- new infos + assert_eq!(mock::validator_current_info(11).exposure.total, 1000 + total_payout_0 / 2); + assert_eq!(mock::validator_current_info(21).exposure.total, 69 + total_payout_0 / 2); // -- slot stake should also be updated. - assert_eq!(Staking::slot_stake(), 69 + total_payout_0 / 2); + assert_eq!(Staking::slot_stake_for_era(Staking::current_era()), 69 + total_payout_0 / 2); - check_exposure_all(); - check_nominator_all(); + check_exposure_all(Staking::current_era()); + check_nominator_all(Staking::current_era()); }); } @@ -1323,8 +1335,6 @@ fn on_free_balance_zero_stash_removes_nominator() { fn switching_roles() { // Test that it should be possible to switch between roles (nominator, validator, idle) with minimal overhead. ExtBuilder::default().nominate(false).build().execute_with(|| { - Timestamp::set_timestamp(1); // Initialize time. - // Reset reward destination for i in &[10, 20] { assert_ok!(Staking::set_payee(Origin::signed(*i), RewardDestination::Controller)); } @@ -1344,20 +1354,7 @@ fn switching_roles() { assert_ok!(Staking::bond(Origin::signed(5), 6, 1000, RewardDestination::Controller)); assert_ok!(Staking::validate(Origin::signed(6), ValidatorPrefs::default())); - // new block - start_session(1); - - // no change - assert_eq_uvec!(validator_controllers(), vec![20, 10]); - - // new block - start_session(2); - - // no change - assert_eq_uvec!(validator_controllers(), vec![20, 10]); - - // new block --> ne era --> new validators - start_session(3); + start_era(1); // with current nominators 10 and 5 have the most stake assert_eq_uvec!(validator_controllers(), vec![6, 10]); @@ -1371,18 +1368,12 @@ fn switching_roles() { // 2 : 2000 self vote + 250 vote. // Winners: 20 and 2 - start_session(4); - assert_eq_uvec!(validator_controllers(), vec![6, 10]); - - start_session(5); - assert_eq_uvec!(validator_controllers(), vec![6, 10]); + start_era(2); - // ne era - start_session(6); assert_eq_uvec!(validator_controllers(), vec![2, 20]); - check_exposure_all(); - check_nominator_all(); + check_exposure_all(Staking::current_era()); + check_nominator_all(Staking::current_era()); }); } @@ -1477,36 +1468,38 @@ fn bond_with_little_staked_value_bounded_by_slot_stake() { assert_ok!(Staking::bond(Origin::signed(1), 2, 1, RewardDestination::Controller)); assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default())); - let total_payout_0 = current_total_payout_for_duration(3000); - assert!(total_payout_0 > 100); // Test is meaningfull if reward something + // reward session 1 + let total_payout_1 = current_total_payout_for_duration(3000); + assert!(total_payout_1 > 100); // Test is meaningfull if reward something reward_all_elected(); start_era(1); // 2 is elected. // and fucks up the slot stake. assert_eq_uvec!(validator_controllers(), vec![20, 10, 2]); - assert_eq!(Staking::slot_stake(), 1); + assert_eq!(Staking::slot_stake_for_era(Staking::current_era()), 1); // Old ones are rewarded. - assert_eq!(Balances::free_balance(&10), init_balance_10 + total_payout_0 / 3); + assert_eq!(Balances::free_balance(&10), init_balance_10 + total_payout_1 / 3); // no rewards paid to 2. This was initial election. assert_eq!(Balances::free_balance(&2), init_balance_2); - let total_payout_1 = current_total_payout_for_duration(3000); - assert!(total_payout_1 > 100); // Test is meaningfull if reward something + // reward session 2 + let total_payout_2 = current_total_payout_for_duration(3000); + assert!(total_payout_2 > 100); // Test is meaningfull if reward something reward_all_elected(); start_era(2); assert_eq_uvec!(validator_controllers(), vec![20, 10, 2]); - assert_eq!(Staking::slot_stake(), 1); + assert_eq!(Staking::slot_stake_for_era(Staking::current_era()), 1); - assert_eq!(Balances::free_balance(&2), init_balance_2 + total_payout_1 / 3); + assert_eq!(Balances::free_balance(&2), init_balance_2 + total_payout_2 / 3); assert_eq!( Balances::free_balance(&10), - init_balance_10 + total_payout_0 / 3 + total_payout_1 / 3, + init_balance_10 + total_payout_1 / 3 + total_payout_2 / 3, ); - check_exposure_all(); - check_nominator_all(); + check_exposure_all(Staking::current_era()); + check_nominator_all(Staking::current_era()); }); } @@ -1542,16 +1535,16 @@ fn phragmen_linear_worse_case_equalize() { assert_eq_uvec!(validator_controllers(), vec![10, 60, 40, 20, 50, 30, 70]); - assert_eq_error_rate!(Staking::stakers(11).total, 3000, 2); - assert_eq_error_rate!(Staking::stakers(21).total, 2255, 2); - assert_eq_error_rate!(Staking::stakers(31).total, 2255, 2); - assert_eq_error_rate!(Staking::stakers(41).total, 1925, 2); - assert_eq_error_rate!(Staking::stakers(51).total, 1870, 2); - assert_eq_error_rate!(Staking::stakers(61).total, 1890, 2); - assert_eq_error_rate!(Staking::stakers(71).total, 1800, 2); + assert_eq_error_rate!(mock::validator_current_info(11).exposure.total, 3000, 2); + assert_eq_error_rate!(mock::validator_current_info(21).exposure.total, 2255, 2); + assert_eq_error_rate!(mock::validator_current_info(31).exposure.total, 2255, 2); + assert_eq_error_rate!(mock::validator_current_info(41).exposure.total, 1925, 2); + assert_eq_error_rate!(mock::validator_current_info(51).exposure.total, 1870, 2); + assert_eq_error_rate!(mock::validator_current_info(61).exposure.total, 1890, 2); + assert_eq_error_rate!(mock::validator_current_info(71).exposure.total, 1800, 2); - check_exposure_all(); - check_nominator_all(); + check_exposure_all(Staking::current_era()); + check_nominator_all(Staking::current_era()); }) } @@ -1571,8 +1564,8 @@ fn new_era_elects_correct_number_of_validators() { Session::on_initialize(System::block_number()); assert_eq!(validator_controllers().len(), 1); - check_exposure_all(); - check_nominator_all(); + check_exposure_all(Staking::current_era()); + check_nominator_all(Staking::current_era()); }) } @@ -1594,8 +1587,8 @@ fn phragmen_should_not_overflow_validators() { // This test will fail this. Will saturate. // check_exposure_all(); - assert_eq!(Staking::stakers(3).total, u64::max_value()); - assert_eq!(Staking::stakers(5).total, u64::max_value()); + assert_eq!(mock::validator_current_info(3).exposure.total, u64::max_value()); + assert_eq!(mock::validator_current_info(5).exposure.total, u64::max_value()); }) } @@ -1616,8 +1609,8 @@ fn phragmen_should_not_overflow_nominators() { assert_eq_uvec!(validator_controllers(), vec![4, 2]); // Saturate. - assert_eq!(Staking::stakers(3).total, u64::max_value()); - assert_eq!(Staking::stakers(5).total, u64::max_value()); + assert_eq!(mock::validator_current_info(3).exposure.total, u64::max_value()); + assert_eq!(mock::validator_current_info(5).exposure.total, u64::max_value()); }) } @@ -1635,8 +1628,8 @@ fn phragmen_should_not_overflow_ultimate() { assert_eq_uvec!(validator_controllers(), vec![4, 2]); // Saturate. - assert_eq!(Staking::stakers(3).total, u64::max_value()); - assert_eq!(Staking::stakers(5).total, u64::max_value()); + assert_eq!(mock::validator_current_info(3).exposure.total, u64::max_value()); + assert_eq!(mock::validator_current_info(5).exposure.total, u64::max_value()); }) } @@ -1651,10 +1644,12 @@ fn reward_validator_slashing_validator_doesnt_overflow() { // Set staker let _ = Balances::make_free_balance_be(&11, stake); - >::insert(&11, Exposure { total: stake, own: stake, others: vec![] }); + + let exposure = Exposure { total: stake, own: stake, others: vec![] }; + let prefs = ValidatorPrefs { commission: Perbill::zero() }; // Check reward - let _ = Staking::reward_validator(&11, reward_slash); + let _ = Staking::reward_validator(&11, &prefs, &exposure, reward_slash); assert_eq!(Balances::total_balance(&11), stake * 2); // Set staker @@ -1664,16 +1659,17 @@ fn reward_validator_slashing_validator_doesnt_overflow() { // only slashes out of bonded stake are applied. without this line, // it is 0. Staking::bond(Origin::signed(2), 20000, stake - 1, RewardDestination::default()).unwrap(); - >::insert(&11, Exposure { total: stake, own: 1, others: vec![ - IndividualExposure { who: 2, value: stake - 1 } - ]}); - + mock::bypass_logic_change_current_exposure(11, Exposure { + total: stake, + own: 1, + others: vec![ IndividualExposure { who: 2, value: stake - 1 }] + }); // Check slashing on_offence_now( &[ OffenceDetails { - offender: (11, Staking::stakers(&11)), + offender: (11, mock::validator_current_info(11).exposure), reporters: vec![], }, ], @@ -1701,11 +1697,11 @@ fn reward_from_authorship_event_handler_works() { >::note_uncle(11, 1); // Not mandatory but must be coherent with rewards - assert_eq!(>::get(), vec![21, 11]); + assert_eq!(Session::validators(), vec![11, 21]); // 21 is rewarded as an uncle producer // 11 is rewarded as a block producer and uncle referencer and uncle producer - assert_eq!(CurrentEraPointsEarned::get().individual, vec![1, 20 + 2 * 3 + 1]); + assert_eq!(CurrentEraPointsEarned::get().individual, vec![20 + 2 * 3 + 1, 1]); assert_eq!(CurrentEraPointsEarned::get().total, 28); }) } @@ -1713,15 +1709,14 @@ fn reward_from_authorship_event_handler_works() { #[test] fn add_reward_points_fns_works() { ExtBuilder::default().build().execute_with(|| { - let validators = >::current_elected(); // Not mandatory but must be coherent with rewards - assert_eq!(validators, vec![21, 11]); + assert_eq!(Session::validators(), vec![11, 21]); >::reward_by_indices(vec![ - (0, 1), (1, 1), + (0, 1), (2, 1), - (1, 1), + (0, 1), ]); >::reward_by_ids(vec![ @@ -1731,7 +1726,7 @@ fn add_reward_points_fns_works() { (11, 1), ]); - assert_eq!(CurrentEraPointsEarned::get().individual, vec![2, 4]); + assert_eq!(CurrentEraPointsEarned::get().individual, vec![4, 2]); assert_eq!(CurrentEraPointsEarned::get().total, 6); }) } @@ -1755,19 +1750,20 @@ fn era_is_always_same_length() { // session changes. ExtBuilder::default().build().execute_with(|| { start_era(1); - assert_eq!(Staking::current_era_start_session_index(), SessionsPerEra::get()); + assert_eq!(Staking::era_start_session_index()[0], SessionsPerEra::get()); start_era(2); - assert_eq!(Staking::current_era_start_session_index(), SessionsPerEra::get() * 2); + assert_eq!(Staking::era_start_session_index()[0], SessionsPerEra::get() * 2); let session = Session::current_index(); ForceEra::put(Forcing::ForceNew); advance_session(); + advance_session(); assert_eq!(Staking::current_era(), 3); - assert_eq!(Staking::current_era_start_session_index(), session + 1); + assert_eq!(Staking::era_start_session_index()[0], session + 2); start_era(4); - assert_eq!(Staking::current_era_start_session_index(), session + SessionsPerEra::get() + 1); + assert_eq!(Staking::era_start_session_index()[0], session + 2 + SessionsPerEra::get()); }); } @@ -1778,7 +1774,7 @@ fn offence_forces_new_era() { &[OffenceDetails { offender: ( 11, - Staking::stakers(&11), + mock::validator_current_info(11).exposure, ), reporters: vec![], }], @@ -1798,7 +1794,7 @@ fn offence_ensures_new_era_without_clobbering() { &[OffenceDetails { offender: ( 11, - Staking::stakers(&11), + mock::validator_current_info(11).exposure, ), reporters: vec![], }], @@ -1812,12 +1808,13 @@ fn offence_ensures_new_era_without_clobbering() { #[test] fn offence_deselects_validator_when_slash_is_zero() { ExtBuilder::default().build().execute_with(|| { + assert!(Session::validators().contains(&11)); assert!(>::exists(11)); on_offence_now( &[OffenceDetails { offender: ( 11, - Staking::stakers(&11), + mock::validator_current_info(11).exposure, ), reporters: vec![], }], @@ -1825,6 +1822,9 @@ fn offence_deselects_validator_when_slash_is_zero() { ); assert_eq!(Staking::force_era(), Forcing::ForceNew); assert!(!>::exists(11)); + start_era(1); + assert!(!Session::validators().contains(&11)); + assert!(!>::exists(11)); }); } @@ -1833,7 +1833,7 @@ fn slashing_performed_according_exposure() { // This test checks that slashing is performed according the exposure (or more precisely, // historical exposure), not the current balance. ExtBuilder::default().build().execute_with(|| { - assert_eq!(Staking::stakers(&11).own, 1000); + assert_eq!(mock::validator_current_info(11).exposure.own, 1000); // Handle an offence with a historical exposure. on_offence_now( @@ -1862,11 +1862,12 @@ fn slash_in_old_span_does_not_deselect() { start_era(1); assert!(>::exists(11)); + assert!(Session::validators().contains(&11)); on_offence_now( &[OffenceDetails { offender: ( 11, - Staking::stakers(&11), + mock::validator_current_info(11).exposure, ), reporters: vec![], }], @@ -1880,6 +1881,7 @@ fn slash_in_old_span_does_not_deselect() { Staking::validate(Origin::signed(10), Default::default()).unwrap(); assert_eq!(Staking::force_era(), Forcing::NotForcing); assert!(>::exists(11)); + assert!(!Session::validators().contains(&11)); start_era(3); @@ -1890,7 +1892,7 @@ fn slash_in_old_span_does_not_deselect() { &[OffenceDetails { offender: ( 11, - Staking::stakers(&11), + mock::validator_current_info(11).exposure, ), reporters: vec![], }], @@ -1901,12 +1903,13 @@ fn slash_in_old_span_does_not_deselect() { // not for zero-slash. assert_eq!(Staking::force_era(), Forcing::NotForcing); assert!(>::exists(11)); + assert!(Session::validators().contains(&11)); on_offence_in_era( &[OffenceDetails { offender: ( 11, - Staking::stakers(&11), + mock::validator_current_info(11).exposure, ), reporters: vec![], }], @@ -1917,6 +1920,7 @@ fn slash_in_old_span_does_not_deselect() { // or non-zero. assert_eq!(Staking::force_era(), Forcing::NotForcing); assert!(>::exists(11)); + assert!(Session::validators().contains(&11)); assert_ledger_consistent(11); }); } @@ -1932,13 +1936,13 @@ fn reporters_receive_their_slice() { #[cfg(not(feature = "equalize"))] let initial_balance = 1125; - assert_eq!(Staking::stakers(&11).total, initial_balance); + assert_eq!(mock::validator_current_info(11).exposure.total, initial_balance); on_offence_now( &[OffenceDetails { offender: ( 11, - Staking::stakers(&11), + mock::validator_current_info(11).exposure, ), reporters: vec![1, 2], }], @@ -1966,13 +1970,13 @@ fn subsequent_reports_in_same_span_pay_out_less() { #[cfg(not(feature = "equalize"))] let initial_balance = 1125; - assert_eq!(Staking::stakers(&11).total, initial_balance); + assert_eq!(mock::validator_current_info(11).exposure.total, initial_balance); on_offence_now( &[OffenceDetails { offender: ( 11, - Staking::stakers(&11), + mock::validator_current_info(11).exposure, ), reporters: vec![1], }], @@ -1988,7 +1992,7 @@ fn subsequent_reports_in_same_span_pay_out_less() { &[OffenceDetails { offender: ( 11, - Staking::stakers(&11), + mock::validator_current_info(11).exposure, ), reporters: vec![1], }], @@ -2012,7 +2016,7 @@ fn invulnerables_are_not_slashed() { assert_eq!(Balances::free_balance(&11), 1000); assert_eq!(Balances::free_balance(&21), 2000); - let exposure = Staking::stakers(&21); + let exposure = mock::validator_current_info(21).exposure; let initial_balance = Staking::slashable_balance_of(&21); let nominator_balances: Vec<_> = exposure.others @@ -2021,11 +2025,11 @@ fn invulnerables_are_not_slashed() { on_offence_now( &[ OffenceDetails { - offender: (11, Staking::stakers(&11)), + offender: (11, mock::validator_current_info(11).exposure), reporters: vec![], }, OffenceDetails { - offender: (21, Staking::stakers(&21)), + offender: (21, mock::validator_current_info(21).exposure), reporters: vec![], }, ], @@ -2059,7 +2063,7 @@ fn dont_slash_if_fraction_is_zero() { &[OffenceDetails { offender: ( 11, - Staking::stakers(&11), + mock::validator_current_info(11).exposure, ), reporters: vec![], }], @@ -2080,7 +2084,7 @@ fn only_slash_for_max_in_era() { on_offence_now( &[ OffenceDetails { - offender: (11, Staking::stakers(&11)), + offender: (11, mock::validator_current_info(11).exposure), reporters: vec![], }, ], @@ -2094,7 +2098,7 @@ fn only_slash_for_max_in_era() { on_offence_now( &[ OffenceDetails { - offender: (11, Staking::stakers(&11)), + offender: (11, mock::validator_current_info(11).exposure), reporters: vec![], }, ], @@ -2107,7 +2111,7 @@ fn only_slash_for_max_in_era() { on_offence_now( &[ OffenceDetails { - offender: (11, Staking::stakers(&11)), + offender: (11, mock::validator_current_info(11).exposure), reporters: vec![], }, ], @@ -2128,7 +2132,7 @@ fn garbage_collection_after_slashing() { on_offence_now( &[ OffenceDetails { - offender: (11, Staking::stakers(&11)), + offender: (11, mock::validator_current_info(11).exposure), reporters: vec![], }, ], @@ -2142,7 +2146,7 @@ fn garbage_collection_after_slashing() { on_offence_now( &[ OffenceDetails { - offender: (11, Staking::stakers(&11)), + offender: (11, mock::validator_current_info(11).exposure), reporters: vec![], }, ], @@ -2165,14 +2169,14 @@ fn garbage_collection_on_window_pruning() { assert_eq!(Balances::free_balance(&11), 1000); - let exposure = Staking::stakers(&11); + let exposure = mock::validator_current_info(11).exposure; assert_eq!(Balances::free_balance(&101), 2000); let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; on_offence_now( &[ OffenceDetails { - offender: (11, Staking::stakers(&11)), + offender: (11, mock::validator_current_info(11).exposure), reporters: vec![], }, ], @@ -2213,8 +2217,8 @@ fn slashing_nominators_by_span_max() { assert_eq!(Staking::slashable_balance_of(&21), 1000); - let exposure_11 = Staking::stakers(&11); - let exposure_21 = Staking::stakers(&21); + let exposure_11 = mock::validator_current_info(11).exposure; + let exposure_21 = mock::validator_current_info(21).exposure; assert_eq!(Balances::free_balance(&101), 2000); let nominated_value_11 = exposure_11.others.iter().find(|o| o.who == 101).unwrap().value; let nominated_value_21 = exposure_21.others.iter().find(|o| o.who == 101).unwrap().value; @@ -2222,7 +2226,7 @@ fn slashing_nominators_by_span_max() { on_offence_in_era( &[ OffenceDetails { - offender: (11, Staking::stakers(&11)), + offender: (11, mock::validator_current_info(11).exposure), reporters: vec![], }, ], @@ -2256,7 +2260,7 @@ fn slashing_nominators_by_span_max() { on_offence_in_era( &[ OffenceDetails { - offender: (21, Staking::stakers(&21)), + offender: (21, mock::validator_current_info(21).exposure), reporters: vec![], }, ], @@ -2279,7 +2283,7 @@ fn slashing_nominators_by_span_max() { on_offence_in_era( &[ OffenceDetails { - offender: (11, Staking::stakers(&11)), + offender: (11, mock::validator_current_info(11).exposure), reporters: vec![], }, ], @@ -2315,7 +2319,7 @@ fn slashes_are_summed_across_spans() { on_offence_now( &[ OffenceDetails { - offender: (21, Staking::stakers(&21)), + offender: (21, mock::validator_current_info(21).exposure), reporters: vec![], }, ], @@ -2340,7 +2344,7 @@ fn slashes_are_summed_across_spans() { on_offence_now( &[ OffenceDetails { - offender: (21, Staking::stakers(&21)), + offender: (21, mock::validator_current_info(21).exposure), reporters: vec![], }, ], @@ -2365,14 +2369,14 @@ fn deferred_slashes_are_deferred() { assert_eq!(Balances::free_balance(&11), 1000); - let exposure = Staking::stakers(&11); + let exposure = mock::validator_current_info(11).exposure; assert_eq!(Balances::free_balance(&101), 2000); let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; on_offence_now( &[ OffenceDetails { - offender: (11, Staking::stakers(&11)), + offender: (11, mock::validator_current_info(11).exposure), reporters: vec![], }, ], @@ -2408,7 +2412,7 @@ fn remove_deferred() { assert_eq!(Balances::free_balance(&11), 1000); - let exposure = Staking::stakers(&11); + let exposure = mock::validator_current_info(11).exposure; assert_eq!(Balances::free_balance(&101), 2000); let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; @@ -2478,7 +2482,7 @@ fn remove_multi_deferred() { assert_eq!(Balances::free_balance(&11), 1000); - let exposure = Staking::stakers(&11); + let exposure = mock::validator_current_info(11).exposure; assert_eq!(Balances::free_balance(&101), 2000); on_offence_now( @@ -2494,7 +2498,7 @@ fn remove_multi_deferred() { on_offence_now( &[ OffenceDetails { - offender: (21, Staking::stakers(&21)), + offender: (21, mock::validator_current_info(21).exposure), reporters: vec![], } ], @@ -2527,3 +2531,19 @@ fn version_initialized() { assert_eq!(::StorageVersion::get(), crate::migration::CURRENT_VERSION); }); } + +// If on_session_ending is called with a decreasing will_apply_at and some era are force then some +// planned era should be overriden +#[test] +fn unplanned_future_era() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Session::current_index(), 0); + Staking::on_session_ending(0, 10); + assert_eq!(Staking::era_start_session_index(), vec![0, 10]); + Staking::on_session_ending(1, 20); + assert_eq!(Staking::era_start_session_index(), vec![0, 10, 20]); + assert_ok!(Staking::force_new_era(Origin::ROOT)); + Staking::on_session_ending(2, 5); + assert_eq!(Staking::era_start_session_index(), vec![0, 5]); + }); +}