From 4a9a7240c3c53565d4fb36266816737aca8ecbc6 Mon Sep 17 00:00:00 2001 From: "R.Rajeshkumar" Date: Mon, 20 Jun 2022 12:54:15 +0530 Subject: [PATCH] Hooks & Slash functionality update Signed-off-by: R.Rajeshkumar --- pallets/staking/src/hooks.rs | 280 +- pallets/staking/src/lib.rs | 1302 ++++++-- pallets/staking/src/mock.rs | 46 +- pallets/staking/src/set.rs | 87 +- pallets/staking/src/slashing.rs | 1020 +++--- pallets/staking/src/tests.rs | 5282 +------------------------------ pallets/staking/src/types.rs | 215 +- 7 files changed, 2149 insertions(+), 6083 deletions(-) diff --git a/pallets/staking/src/hooks.rs b/pallets/staking/src/hooks.rs index 0c305ecb39c..b62a50583fd 100644 --- a/pallets/staking/src/hooks.rs +++ b/pallets/staking/src/hooks.rs @@ -21,7 +21,10 @@ use super::{ SessionValidatorReward, SlashRewardProportion, Staked, Store, Total, }; // use crate::slashing; -use crate::types::{ValidatorSnapshot, ValidatorSnapshotOf}; +use crate::{ + slashing::SlashParams, + types::{ValidatorSnapshot, ValidatorSnapshotOf}, +}; use frame_support::{ pallet_prelude::*, traits::{Currency, Get, Imbalance, OnUnbalanced}, @@ -57,6 +60,135 @@ impl OnUnbalanced> for Pallet { } } +/// Add reward points to block authors: +/// * 20 points to the block producer for producing a (non-uncle) block in the relay chain, +/// * 2 points to the block producer for each reference to a previously unreferenced uncle, and +/// * 1 point to the producer of each referenced uncle block. +impl pallet_authorship::EventHandler for Pallet +where + T: Config + pallet_authorship::Config + pallet_session::Config, +{ + fn note_author(author: T::AccountId) { + log::trace!("note_author:[{:#?}] - Author[{:#?}]", line!(), author); + Self::reward_by_ids(vec![(author, 20)]) + } + fn note_uncle(uncle_author: T::AccountId, _age: T::BlockNumber) { + log::trace!("note_uncle:[{:#?}] - uncle_author[{:#?}]", line!(), uncle_author); + if let Some(block_author) = >::author() { + Self::reward_by_ids(vec![(block_author, 2), (uncle_author, 1)]) + } else { + log::error!("block author not set, this should never happen"); + } + } +} +/// In this implementation `new_session(session)` must be called before `end_session(session-1)` +/// i.e. the new session must be planned before the ending of the previous session. +/// +/// Once the first new_session is planned, all session must start and then end in order. +impl pallet_session::SessionManager for Pallet { + fn new_session(new_index: SessionIndex) -> Option> { + log::trace!("new_session:[{:#?}] - Sess-idx[{:#?}]", line!(), new_index); + + let current_block_number = system::Pallet::::block_number(); + + // select top collator validators for next round + let (validator_count, total_staked) = match Self::select_session_validators(new_index) { + Ok((validator_count, total_staked)) => (validator_count, total_staked), + Err(_) => return None, + }; + + // snapshot total stake + >::insert(new_index, >::get()); + + Self::deposit_event(Event::NewSession( + current_block_number, + new_index, + validator_count, + total_staked, + )); + + log::debug!( + "new_session:[{:#?}] - Event::NewSession(SI[{}],VC[{}],TS[{:#?}])", + line!(), + new_index, + validator_count, + total_staked, + ); + + Some(Self::selected_validators().to_vec()) + } + fn start_session(start_index: SessionIndex) { + log::trace!("start_session:[{:#?}] - Sess-idx[{:#?}]", line!(), start_index); + + >::put(start_index); + + let bonding_duration = T::BondedDuration::get(); + + >::mutate(|bonded| { + let _ = match bonded.try_push(start_index) { + Err(_) => { + log::error!( + "start_session:[{:#?}] - Error, Could be BondedSessions Overflow", + line!() + ); + return (); + } + Ok(_) => (), + }; + + if start_index > bonding_duration { + let first_kept = start_index - bonding_duration; + + // prune out everything that's from before the first-kept index. + let n_to_prune = bonded + .to_vec() + .iter() + .take_while(|&&session_idx| session_idx < first_kept) + .count(); + + for prune_session in bonded.to_vec().drain(..n_to_prune) { + // Clear the DB cached state of last session + Self::clear_session_information(prune_session); + } + + if let Some(&first_session) = bonded.first() { + T::SessionInterface::prune_historical_up_to(first_session); + } + } + }); + + // execute all delayed validator exits + Self::execute_delayed_validator_exits(start_index); + + // Handle the unapplied deferd slashes + Self::apply_unapplied_slashes(start_index); + + log::trace!("start_session:[{:#?}] - Exit!!! Sess-idx[{:#?}]", line!(), start_index); + } + fn end_session(end_index: SessionIndex) { + log::trace!("end_session:[{:#?}] - Sess-idx[{:#?}]", line!(), end_index); + + if Self::active_session() == end_index { + let payout = Self::session_accumulated_balance(end_index); + + // Set ending session reward. + >::insert(&end_index, payout); + + // pay all stakers for T::BondedDuration rounds ago + Self::pay_stakers(end_index); + + // Clear the DB cached state of last session + Self::clear_session_information(Self::active_session()); + } else { + log::error!( + "end_session:[{:#?}] - Something wrong (CSI[{}], ESI[{}])", + line!(), + Self::active_session(), + end_index, + ); + } + } +} /// Means for interacting with a specialized version of the `session` trait. /// /// This is needed because `Staking` sets the `ValidatorIdOf` of the `pallet_session::Config` @@ -99,3 +231,149 @@ where >::prune_up_to(up_to); } } + +impl historical::SessionManager> + for Pallet +{ + fn new_session( + new_index: SessionIndex, + ) -> Option)>> { + >::new_session(new_index).map(|validators| { + validators + .into_iter() + .map(|v| { + let validator_inst = Self::at_stake(new_index, &v); + (v, validator_inst) + }) + .collect() + }) + } + fn start_session(start_index: SessionIndex) { + >::start_session(start_index) + } + fn end_session(end_index: SessionIndex) { + >::end_session(end_index) + } +} + +/// This is intended to be used with `FilterHistoricalOffences`. +impl OnOffenceHandler, Weight> for Pallet +where + T: pallet_session::Config::AccountId>, + T: pallet_session::historical::Config< + FullIdentification = ValidatorSnapshot, + FullIdentificationOf = ValidatorSnapshotOf, + >, + T::SessionHandler: pallet_session::SessionHandler<::AccountId>, + T::SessionManager: pallet_session::SessionManager<::AccountId>, + T::ValidatorIdOf: Convert<::AccountId, Option<::AccountId>>, +{ + fn on_offence( + offenders: &[OffenceDetails>], + slash_fraction: &[Perbill], + slash_session: SessionIndex, + disable_strategy: DisableStrategy, + ) -> Weight { + log::trace!( + "on_offence:[{:#?}] - Sess-idx [{:#?}] | Slash-Frac [{:#?}]", + line!(), + slash_session, + slash_fraction, + ); + + let reward_proportion = >::get(); + let mut consumed_weight: Weight = 0; + let mut add_db_reads_writes = |reads, writes| { + consumed_weight = consumed_weight.saturating_add(T::DbWeight::get().reads_writes(reads, writes)); + }; + + let active_session = Self::active_session(); + add_db_reads_writes(1, 0); + + let window_start = active_session.saturating_sub(T::BondedDuration::get()); + let slash_defer_duration = T::SlashDeferDuration::get(); + + let invulnerables = Self::invulnerables(); + add_db_reads_writes(1, 0); + + log::trace!("on_offence:[{:#?}] - Invulnerables[{:#?}]", line!(), invulnerables,); + + for (details, slash_fraction) in offenders.iter().zip(slash_fraction) { + let (controller, exposure) = &details.offender; + + // Skip if the validator is invulnerable. + if invulnerables.contains(controller) { + continue; + } + + let slash_param: SlashParams = SlashParams { + controller: controller.clone(), + slash: *slash_fraction, + exposure: exposure.clone(), + slash_session, + window_start, + now: active_session, + reward_proportion, + disable_strategy, + }; + + match slash_param.compute_slash() { + Err(err) => { + log::error!("on_offence:[{:#?}] - compute_slash Err[{:#?}]", line!(), err,); + } + Ok(None) => { + log::trace!("on_offence:[{:#?}] - NOP", line!(),); + add_db_reads_writes(4 /* fetch_spans */, 5 /* kick_out_if_recent */); + } + Ok(Some(mut unapplied)) => { + let nominators_len = unapplied.others.len() as u64; + let reporters_len = details.reporters.to_vec().len() as u64; + + { + let upper_bound = 1 /* Validator/NominatorSlashInEra */ + 2 /* fetch_spans */; + let rw = upper_bound + nominators_len * upper_bound; + add_db_reads_writes(rw, rw); + } + + unapplied.reporters = + >::try_from(details.reporters.clone()) + .expect("OnOffenceHandler Reporters Overflow Error"); + + if slash_defer_duration == 0 { + // apply right away. + unapplied.apply_slash(); + + let slash_cost = (6, 5); + let reward_cost = (2, 2); + add_db_reads_writes( + (1 + nominators_len) * slash_cost.0 + reward_cost.0 * reporters_len, + (1 + nominators_len) * slash_cost.1 + reward_cost.1 * reporters_len, + ); + } else { + // defer to end of some `slash_defer_duration` from now. + let apply_at = active_session.saturating_add(slash_defer_duration); + + let unapplied_for_event = unapplied.clone(); + + ::UnappliedSlashes::mutate(apply_at, move |for_later| { + match for_later.try_push(unapplied) { + Err(_) => { + log::error!("on_offence:[{:#?}] - UnappliedSlashes Overflow", line!()); + } + Ok(_) => {} + } + }); + + >::deposit_event(Event::DeferredUnappliedSlash( + active_session, + unapplied_for_event.validator, + )); + + add_db_reads_writes(1, 1); + } + } + } + } + consumed_weight + } +} diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs index 8ad9d292485..79240e9530b 100644 --- a/pallets/staking/src/lib.rs +++ b/pallets/staking/src/lib.rs @@ -21,13 +21,18 @@ #![cfg_attr(not(feature = "std"), no_std)] -// #[cfg(test)] -// mod mock; +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; use frame_support::pallet; mod hooks; mod set; +mod slashing; mod types; +mod weights; pub use pallet::*; @@ -35,11 +40,10 @@ pub use pallet::*; pub mod pallet { use super::*; use frame_support::{ - bounded_vec, pallet_prelude::*, traits::{ - Currency, ExistenceRequirement, Get, Imbalance, LockIdentifier, LockableCurrency, OnUnbalanced, Polling, - ValidatorRegistration, WithdrawReasons, + Currency, ExistenceRequirement, Get, LockIdentifier, LockableCurrency, OnUnbalanced, ValidatorRegistration, + WithdrawReasons, }, BoundedVec, PalletId, }; @@ -51,8 +55,14 @@ pub mod pallet { use sp_staking::SessionIndex; use sp_std::{convert::From, prelude::*}; + use hooks::SessionInterface; + use types::{Bond, Nominator, RewardPoint, SpanIndex, StakeReward, UnlockChunk, Validator, ValidatorSnapshot}; + use weights::WeightInfo; + use set::OrderedSet; - use types::{Bond, Nominator, RewardPoint, StakeReward, UnlockChunk, Validator, ValidatorSnapshot}; + + pub(crate) type StakingInvulnerables = + BoundedVec<::AccountId, ::MaxInvulnerableStakers>; pub(crate) type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -65,26 +75,78 @@ pub mod pallet { type Event: From> + IsType<::Event>; /// The staking balance. type Currency: LockableCurrency; + /// Handler for the unbalanced reduction when slashing a staker. + type Slash: OnUnbalanced>; /// Number of sessions that staked fund remain bonded for type BondedDuration: Get; - /// Maximum validators allowed to join the pool. + /// Number of sessions that slashes are deferred by, after computation. + type SlashDeferDuration: Get; + /// Minimum number of selected validators every round #[pallet::constant] - type DefaultStakingMaxValidators: Get; + type MinSelectedValidators: Get; + /// Maximum invulnerable Stakers + #[pallet::constant] + type MaxInvulnerableStakers: Get; /// Maximum nominators per validator + #[pallet::constant] type MaxNominatorsPerValidator: Get; /// Maximum validators per nominator #[pallet::constant] type MaxValidatorPerNominator: Get; - /// staking pallet Lock Identifier used for set_lock() + /// Maximum Slash Reporters #[pallet::constant] - type StakingLockId: Get; + type MaxSlashReporters: Get; + /// Fee due to validators, set at genesis + #[pallet::constant] + type DefaultValidatorFee: Get; + /// Default Slash reward propostion, set at genesis + #[pallet::constant] + type DefaultSlashRewardProportion: Get; + /// The proportion of the slashing reward to be paid out on the first slashing detection. + #[pallet::constant] + type DefaultSlashRewardFraction: Get; + /// Maximum validators allowed to join the pool. + #[pallet::constant] + type DefaultStakingMaxValidators: Get; + /// Minimum stake required for any account to be in `SelectedCandidates` for the session + #[pallet::constant] + type DefaultStakingMinStakeSessionSelection: Get>; + /// Minimum stake required for any account to be a validator candidate + #[pallet::constant] + type DefaultStakingMinValidatorBond: Get>; + /// Minimum stake for any registered on-chain account to nominate + #[pallet::constant] + type DefaultStakingMinNominationChillThreshold: Get>; + /// Minimum stake for any registered on-chain account to become a nominator + #[pallet::constant] + type DefaultStakingMinNominatorTotalBond: Get>; + /// Tokens have been minted and are unused for validator-reward. + /// See [Era payout](./index.html#era-payout). + type RewardRemainder: OnUnbalanced>; + /// Interface for interacting with a session module. + type SessionInterface: SessionInterface; + /// Validate a user is registered + type ValidatorRegistration: ValidatorRegistration; /// This pallet's module id. Used to derivate a dedicated account id to store session /// rewards for validators and nominators in. #[pallet::constant] type PalletId: Get; + /// staking pallet Lock Identifier used for set_lock() + #[pallet::constant] + type StakingLockId: Get; /// Max number of unbond request supported by queue #[pallet::constant] - type MaxChunkUnlock: Get + MaxEncodedLen + Clone; + type MaxChunkUnlock: Get; + /// Max number of Unapplied Slash + #[pallet::constant] + type MaxUnAppliedSlash: Get; + /// Max number of Slash Spans + #[pallet::constant] + type MaxSlashSpan: Get; + /// The origin which can cancel a deferred slash. Root can always do this. + type CancelOrigin: EnsureOrigin; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; } #[pallet::pallet] @@ -92,10 +154,104 @@ pub mod pallet { pub struct Pallet(PhantomData); #[pallet::hooks] - impl Hooks> for Pallet {} + impl Hooks> for Pallet { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + // migrations::v1::PoAToStaking::::pre_upgrade() + Ok(()) + } + + fn on_runtime_upgrade() -> frame_support::weights::Weight { + // migrations::v1::PoAToStaking::::on_runtime_upgrade() + 0u64 + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + // migrations::v1::PoAToStaking::::post_upgrade() + Ok(()) + } + } #[pallet::call] impl Pallet { + /// Set the validators who cannot be slashed (if any). + /// + /// The dispatch origin must be Root. + #[pallet::weight(T::WeightInfo::set_invulnerables(invulnerables.len() as u32))] + pub fn set_invulnerables(origin: OriginFor, invulnerables: Vec) -> DispatchResultWithPostInfo { + T::CancelOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?; + + let bounded_invulnerables: StakingInvulnerables = + BoundedVec::try_from(invulnerables).map_err(|_| >::InvulnerablesOverflow)?; + + >::put(&bounded_invulnerables); + Self::deposit_event(Event::NewInvulnerables(bounded_invulnerables)); + Ok(().into()) + } + /// Set the total number of validator selected per round + /// - changes are not applied until the start of the next round + #[pallet::weight(T::WeightInfo::set_total_validator_per_round(*new))] + pub fn set_total_validator_per_round(origin: OriginFor, new: u32) -> DispatchResultWithPostInfo { + T::CancelOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?; + ensure!(new >= T::MinSelectedValidators::get(), >::CannotSetBelowMin); + let old = >::get(); + >::put(new); + Self::deposit_event(Event::TotalSelectedSet(old, new)); + Ok(().into()) + } + #[pallet::weight(T::WeightInfo::set_staking_limits())] + pub fn set_staking_limits( + origin: OriginFor, + max_stake_validators: u32, + min_stake_session_selection: BalanceOf, + min_validator_bond: BalanceOf, + min_nominator_total_bond: BalanceOf, + min_nominator_chill_threshold: BalanceOf, + ) -> DispatchResultWithPostInfo { + T::CancelOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?; + + ensure!(max_stake_validators > 0, >::InvalidArguments); + ensure!(min_stake_session_selection > Zero::zero(), >::InvalidArguments); + ensure!(min_validator_bond > Zero::zero(), >::InvalidArguments); + ensure!(min_nominator_total_bond > Zero::zero(), >::InvalidArguments); + ensure!( + min_nominator_chill_threshold > Zero::zero(), + >::InvalidArguments + ); + + let old_max_stake_validators = >::get(); + >::set(max_stake_validators); + + let old_min_stake_session_selection = >::get(); + >::set(min_stake_session_selection); + + let old_min_validator_bond = >::get(); + >::set(min_validator_bond); + + let old_min_nominator_total_bond = >::get(); + >::set(min_nominator_total_bond); + + let old_min_nominator_chill_threshold = >::get(); + >::set(min_nominator_chill_threshold); + + Self::deposit_event(Event::NewStakingLimits( + old_max_stake_validators, + max_stake_validators, + old_min_stake_session_selection, + min_stake_session_selection, + old_min_validator_bond, + min_validator_bond, + old_min_nominator_total_bond, + min_nominator_total_bond, + old_min_nominator_chill_threshold, + min_nominator_chill_threshold, + )); + + Self::active_stake_reconciliation(); + + Ok(().into()) + } /// Join the set of validators pool #[pallet::weight(1000)] pub fn validator_join_pool(origin: OriginFor, bond: BalanceOf) -> DispatchResultWithPostInfo { @@ -177,12 +333,15 @@ pub mod pallet { let now = Self::active_session(); let when = now.saturating_add(T::BondedDuration::get()); - >::mutate(|exits| { - exits.insert(Bond { - owner: validator.clone(), - amount: when, - }); - }); + >::try_mutate(|exits| -> DispatchResultWithPostInfo { + exits + .insert(Bond { + owner: validator.clone(), + amount: when, + }) + .map_err(|_| >::ExitQueueOverflow)?; + Ok(().into()) + })?; >::mutate(&validator, |maybe_validator| { if let Some(state) = maybe_validator { @@ -190,9 +349,13 @@ pub mod pallet { } }); - >::mutate(|validators| { - validators.remove(&Bond::from_owner(validator.clone())); - }); + >::try_mutate(|validators| -> DispatchResultWithPostInfo { + validators.remove(&Bond::from_owner(validator.clone())).map_err(|_| { + log::error!("validator_exit_pool:[{:#?}] - ValidatorPool Update Failure", line!(),); + >::OrderedSetFailure + })?; + Ok(().into()) + })?; Self::deposit_event(Event::ValidatorScheduledExit(now, validator, when)); Ok(().into()) @@ -220,25 +383,23 @@ pub mod pallet { >::ValidatorBondBelowMin, ); - >::mutate(validator.clone(), |maybe_validator| { - if let Some(state) = maybe_validator { - let before = state.bond; - state.bond_more(more); - T::Currency::set_lock(T::StakingLockId::get(), &validator, state.bond, WithdrawReasons::all()); - let after = state.bond; - state.go_online(); - if state.is_active() { - Self::update_validators_pool( - validator.clone(), - state.bond.saturating_add(state.nomi_bond_total), - ); - } - >::mutate(|x| *x = x.saturating_add(more)); - Self::deposit_event(Event::ValidatorBondedMore(validator.clone(), before, after)); + >::try_mutate(&validator, |maybe_validator| -> DispatchResultWithPostInfo { + let state = maybe_validator.as_mut().ok_or(>::ValidatorDNE)?; + let before = state.bond; + state.bond_more(more); + T::Currency::set_lock(T::StakingLockId::get(), &validator, state.bond, WithdrawReasons::all()); + let after = state.bond; + state.go_online(); + if state.is_active() { + Self::update_validators_pool(&validator, state.bond.saturating_add(state.nomi_bond_total)) + .map_err(|_| >::ValidatorPoolOverflow)?; } - }); + >::mutate(|x| *x = x.saturating_add(more)); + Self::deposit_event(Event::ValidatorBondedMore(validator.clone(), before, after)); + Ok(().into()) + })?; - // Self::validator_stake_reconciliation(&validator); + Self::validator_stake_reconciliation(&validator)?; Ok(().into()) } @@ -268,18 +429,21 @@ pub mod pallet { // But for upcoming validator selection, // State in validator pool is updated with ( validator bond + nominator bond). if state.is_active() { - Self::update_validators_pool(validator.clone(), state.bond.saturating_add(state.nomi_bond_total)); + Self::update_validators_pool(&validator, state.bond.saturating_add(state.nomi_bond_total)) + .map_err(|_| >::ValidatorPoolOverflow)?; } // Update the overall total, since there is change in // active total. >::mutate(|x| *x = x.saturating_sub(less)); - // T::Currency::unreserve(&validator, less); - state.unlocking.try_push(UnlockChunk { - value: less, - session_idx: Self::active_session().saturating_add(T::BondedDuration::get()), - }); + state + .unlocking + .try_push(UnlockChunk { + value: less, + session_idx: Self::active_session().saturating_add(T::BondedDuration::get()), + }) + .map_err(|_| >::UnlockOverFlowError)?; >::insert(&validator, state); Self::deposit_event(Event::ValidatorBondedLess(validator, before, after)); @@ -387,7 +551,8 @@ pub mod pallet { validator_state.total = validator_state.total.saturating_add(amount); let validator_new_total = validator_state.total; if validator_state.is_active() { - Self::update_validators_pool(validator.clone(), validator_state.total); + Self::update_validators_pool(&validator, validator_state.total) + .map_err(|_| >::ValidatorPoolOverflow)?; } >::mutate(|x| *x = x.saturating_add(amount)); @@ -415,7 +580,7 @@ pub mod pallet { let do_force = nominations_inner.len() == 1; - Self::nominator_revokes_validator(acc, validator, do_force) + Self::nominator_revokes_validator(&acc, &validator, do_force) } /// Quit the set of nominators and, by implication, revoke all ongoing nominations // #[pallet::weight(T::WeightInfo::nominator_denominate_all())] @@ -423,8 +588,6 @@ pub mod pallet { pub fn nominator_denominate_all(origin: OriginFor) -> DispatchResultWithPostInfo { let acc = ensure_signed(origin)?; - let nominator = >::get(&acc).ok_or(>::NominatorDNE)?; - let nominator_state = >::get(&acc).ok_or(>::NominatorDNE)?; let nominations_inner: Vec>> = nominator_state @@ -433,7 +596,7 @@ pub mod pallet { .map_err(|_| >::OrderedSetFailure)?; for bond in nominations_inner { - Self::nominator_revokes_validator(acc.clone(), bond.owner.clone(), true)?; + Self::nominator_revokes_validator(&acc, &bond.owner, true)?; } Ok(().into()) @@ -478,11 +641,12 @@ pub mod pallet { ); let before = validator_state.total; - validator_state.inc_nominator(nominator.clone(), more); + validator_state.inc_nominator(nominator.clone(), more)?; let after = validator_state.total; >::mutate(|x| *x = x.saturating_add(more)); if validator_state.is_active() { - Self::update_validators_pool(validator.clone(), validator_state.total); + Self::update_validators_pool(&validator, validator_state.total) + .map_err(|_| >::ValidatorPoolOverflow)?; } >::insert(&validator, validator_state); >::insert(&nominator, nominations); @@ -523,17 +687,20 @@ pub mod pallet { let mut validator_state = >::get(&validator).ok_or(>::ValidatorDNE)?; - nominations.unlocking.try_push(UnlockChunk { - value: less, - session_idx: Self::active_session().saturating_add(T::BondedDuration::get()), - }); + nominations + .unlocking + .try_push(UnlockChunk { + value: less, + session_idx: Self::active_session().saturating_add(T::BondedDuration::get()), + }) + .map_err(|_| >::UnlockOverFlowError)?; let before = validator_state.bond.saturating_add(validator_state.nomi_bond_total); - validator_state.dec_nominator(nominator.clone(), less); + validator_state.dec_nominator(&nominator, less)?; let after = validator_state.bond.saturating_add(validator_state.nomi_bond_total); >::mutate(|x| *x = x.saturating_sub(less)); if validator_state.is_active() { - Self::update_validators_pool(validator.clone(), after); + Self::update_validators_pool(&validator, after).map_err(|_| >::ValidatorPoolOverflow)?; } >::insert(&validator, validator_state); @@ -562,122 +729,118 @@ pub mod pallet { ensure!(from_validator != to_validator, >::ValidatorDNE); - >::try_mutate_exists( - nominator_acc.clone(), - |maybe_nominator| -> DispatchResultWithPostInfo { - let mut nominator_state = maybe_nominator.as_mut().ok_or(>::NominatorDNE)?; + >::try_mutate(nominator_acc.clone(), |maybe_nominator| -> DispatchResultWithPostInfo { + let mut nominator_state = maybe_nominator.as_mut().ok_or(>::NominatorDNE)?; - let nominations_inner: Vec>> = nominator_state - .nominations - .get_inner() - .map_err(|_| >::OrderedSetFailure)?; + let nominations_inner: Vec>> = nominator_state + .nominations + .get_inner() + .map_err(|_| >::OrderedSetFailure)?; - ensure!( - (nominations_inner.len() as u32) <= T::MaxValidatorPerNominator::get(), - >::ExceedMaxValidatorPerNom, - ); + ensure!( + (nominations_inner.len() as u32) <= T::MaxValidatorPerNominator::get(), + >::ExceedMaxValidatorPerNom, + ); - let mut to_validator_state = - >::get(&to_validator).ok_or(>::ValidatorDNE)?; + let mut to_validator_state = >::get(&to_validator).ok_or(>::ValidatorDNE)?; - let valid_nominations_inner: Vec>> = to_validator_state - .nominators - .get_inner() - .map_err(|_| >::OrderedSetFailure)?; + let valid_nominations_inner: Vec>> = to_validator_state + .nominators + .get_inner() + .map_err(|_| >::OrderedSetFailure)?; - ensure!( - (valid_nominations_inner.len() as u32) < T::MaxNominatorsPerValidator::get(), - >::TooManyNominators, - ); + ensure!( + (valid_nominations_inner.len() as u32) < T::MaxNominatorsPerValidator::get(), + >::TooManyNominators, + ); - let old_active_bond = nominator_state.active_bond; - let remaining = nominator_state.rm_nomination(from_validator.clone(), false)?; + let old_active_bond = nominator_state.active_bond; + let remaining = nominator_state.rm_nomination(&from_validator, false)?; - let mut total_nomination_amount = old_active_bond.saturating_sub(remaining); - nominator_state.total = nominator_state.total.saturating_sub(total_nomination_amount); + let mut total_nomination_amount = old_active_bond.saturating_sub(remaining); + nominator_state.total = nominator_state.total.saturating_sub(total_nomination_amount); - total_nomination_amount = total_nomination_amount.saturating_add(amount); + total_nomination_amount = total_nomination_amount.saturating_add(amount); - if unfreeze_bond { - total_nomination_amount = total_nomination_amount.saturating_add(nominator_state.frozen_bond); - } + if unfreeze_bond { + total_nomination_amount = total_nomination_amount.saturating_add(nominator_state.frozen_bond); + } - ensure!( - total_nomination_amount >= >::get(), - >::NominationBelowMin - ); + ensure!( + total_nomination_amount >= >::get(), + >::NominationBelowMin + ); - let nominator_free_balance = T::Currency::free_balance(&nominator_acc); + let nominator_free_balance = T::Currency::free_balance(&nominator_acc); - ensure!( - nominator_free_balance >= nominator_state.total.saturating_add(total_nomination_amount), - >::InsufficientBalance - ); + ensure!( + nominator_free_balance >= nominator_state.total.saturating_add(total_nomination_amount), + >::InsufficientBalance + ); - let add_nomination_status = nominator_state.add_nomination( - Bond { - owner: to_validator.clone(), - amount: total_nomination_amount, - }, - unfreeze_bond, - )?; - - if add_nomination_status { - // Validator is new to the nomination pool - let nomination = Bond { - owner: nominator_acc.clone(), - amount: total_nomination_amount, - }; - to_validator_state.nominators.insert(nomination); - to_validator_state.inc_nominator(nominator_acc.clone(), total_nomination_amount); - } else { - // Validator already exist in nomination pool - let _ = nominator_state.inc_nomination( - to_validator.clone(), - total_nomination_amount, - unfreeze_bond, - )?; - to_validator_state.inc_nominator(nominator_acc.clone(), total_nomination_amount); - } + let add_nomination_status = nominator_state.add_nomination( + Bond { + owner: to_validator.clone(), + amount: total_nomination_amount, + }, + unfreeze_bond, + )?; - ensure!( - nominator_state.total >= >::get(), - >::NominatorBondBelowMin, - ); + if add_nomination_status { + // Validator is new to the nomination pool + let nomination = Bond { + owner: nominator_acc.clone(), + amount: total_nomination_amount, + }; + to_validator_state + .nominators + .insert(nomination) + .map_err(|_| >::NominationOverflow)?; + to_validator_state.inc_nominator(nominator_acc.clone(), total_nomination_amount)?; + } else { + // Validator already exist in nomination pool + let _ = + nominator_state.inc_nomination(to_validator.clone(), total_nomination_amount, unfreeze_bond)?; + to_validator_state.inc_nominator(nominator_acc.clone(), total_nomination_amount)?; + } - T::Currency::set_lock( - T::StakingLockId::get(), - &nominator_acc, - nominator_state.total, - WithdrawReasons::all(), - ); + ensure!( + nominator_state.total >= >::get(), + >::NominatorBondBelowMin, + ); - if to_validator_state.is_active() { - Self::update_validators_pool(to_validator.clone(), to_validator_state.total); - } - // Already ensured nominator is part of validator state. - // So ignoring the return value. - let _ = Self::nominator_leaves_validator(nominator_acc.clone(), from_validator.clone())?; - >::mutate(|x| *x = x.saturating_add(amount)); - >::insert(&to_validator, to_validator_state.clone()); - - // Get the latest source validator info here - // Error should not occur here. - let from_validator_state = - >::get(&from_validator).ok_or(>::ValidatorDNE)?; - - Self::deposit_event(Event::NominationMoved( - nominator_acc, - nominator_state.total, - from_validator, - from_validator_state.total, - to_validator, - to_validator_state.total, - )); + T::Currency::set_lock( + T::StakingLockId::get(), + &nominator_acc, + nominator_state.total, + WithdrawReasons::all(), + ); - Ok(().into()) - }, - )?; + if to_validator_state.is_active() { + Self::update_validators_pool(&to_validator, to_validator_state.total) + .map_err(|_| >::ValidatorPoolOverflow)?; + } + // Already ensured nominator is part of validator state. + // So ignoring the return value. + let _ = Self::nominator_leaves_validator(&nominator_acc, &from_validator)?; + >::mutate(|x| *x = x.saturating_add(amount)); + >::insert(&to_validator, to_validator_state.clone()); + + // Get the latest source validator info here + // Error should not occur here. + let from_validator_state = >::get(&from_validator).ok_or(>::ValidatorDNE)?; + + Self::deposit_event(Event::NominationMoved( + nominator_acc, + nominator_state.total, + from_validator, + from_validator_state.total, + to_validator, + to_validator_state.total, + )); + + Ok(().into()) + })?; Ok(().into()) } @@ -686,6 +849,8 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { + /// Updated InVulnerable validator list \[validator_list\], + NewInvulnerables(StakingInvulnerables), /// Updated total validators per session \[old, new\], TotalSelectedSet(u32, u32), /// Updated staking config, maximum Validators allowed to join the validators pool @@ -840,10 +1005,29 @@ pub mod pallet { OrderedSetFailure, /// Nomination Overflow. NominationOverflow, + /// Invulnerables Overflow. + InvulnerablesOverflow, + /// Exit Queue Overflow. + ExitQueueOverflow, + /// Validator Pool Overflow. + ValidatorPoolOverflow, /// Nomination Invalid InvalidNomination, + /// Slash Reporters Overflow + ReportersOverflow, + /// Stake Reward Overflow + StakeRewardOverflow, + /// Stake Reward Storage Error + StakeRewardDNE, } + /// Any validators that may never be slashed or forcibly kicked. It's a Vec since they're + /// easy to initialize and the performance hit is minimal (we expect no more than four + /// invulnerables) and restricted to testnets. + #[pallet::storage] + #[pallet::getter(fn invulnerables)] + pub(crate) type Invulnerables = StorageValue<_, StakingInvulnerables, ValueQuery>; + /// Maximum Validators allowed to join the validators pool #[pallet::storage] #[pallet::getter(fn staking_max_validators)] @@ -884,6 +1068,45 @@ pub mod pallet { #[pallet::getter(fn validator_fee)] pub(crate) type ValidatorFee = StorageValue<_, Perbill, ValueQuery>; + /// Get validator state associated with an account if account is collating else None + #[pallet::storage] + #[pallet::getter(fn validator_state)] + pub(crate) type ValidatorState = StorageMap< + _, + Twox64Concat, + T::AccountId, + Validator, + OptionQuery, + >; + + /// Get nominator state associated with an account if account is nominating else None + #[pallet::storage] + #[pallet::getter(fn nominator_state)] + pub(crate) type NominatorState = StorageMap< + _, + Twox64Concat, + T::AccountId, + Nominator, + OptionQuery, + >; + + /// The total validators selected every round + #[pallet::storage] + #[pallet::getter(fn total_selected)] + pub(crate) type TotalSelected = StorageValue<_, u32, ValueQuery>; + + /// The validators selected for the current round + #[pallet::storage] + #[pallet::getter(fn selected_validators)] + pub(crate) type SelectedValidators = + StorageValue<_, BoundedVec, ValueQuery>; + + /// The pool of validator validators, each with their total backing stake + #[pallet::storage] + #[pallet::getter(fn validator_pool)] + pub(crate) type ValidatorPool = + StorageValue<_, OrderedSet>, T::DefaultStakingMaxValidators>, ValueQuery>; + /// A queue of validators awaiting exit `BondedDuration` delay after request #[pallet::storage] #[pallet::getter(fn exit_queue)] @@ -944,69 +1167,270 @@ pub mod pallet { #[pallet::getter(fn slash_reward_proportion)] pub(crate) type SlashRewardProportion = StorageValue<_, Perbill, ValueQuery>; - /// Get validator state associated with an account if account is collating else None + /// Snapshot of validator slash state #[pallet::storage] - #[pallet::getter(fn validator_state)] - pub(crate) type ValidatorState = StorageMap< + #[pallet::getter(fn slashing_spans)] + pub(crate) type SlashingSpans = + StorageMap<_, Twox64Concat, T::AccountId, slashing::SlashingSpans, OptionQuery>; + + /// Snapshot of validator slash state + #[pallet::storage] + #[pallet::getter(fn span_slash)] + pub(crate) type SpanSlash = + StorageMap<_, Twox64Concat, (T::AccountId, SpanIndex), slashing::SpanRecord>, ValueQuery>; + + /// All slashing events on validators, mapped by session to the highest + /// slash proportion and slash value of the session. + #[pallet::storage] + #[pallet::getter(fn validator_slash_in_session)] + pub(crate) type ValidatorSlashInSession = StorageDoubleMap< _, Twox64Concat, + SessionIndex, + Twox64Concat, T::AccountId, - Validator, + (Perbill, BalanceOf), OptionQuery, >; - /// Get nominator state associated with an account if account is nominating else None + /// All slashing events on nominators, + /// mapped by session to the highest slash value of the session. #[pallet::storage] - #[pallet::getter(fn nominator_state)] - pub(crate) type NominatorState = StorageMap< + #[pallet::getter(fn nominator_slash_in_session)] + pub(crate) type NominatorSlashInSession = + StorageDoubleMap<_, Twox64Concat, SessionIndex, Twox64Concat, T::AccountId, BalanceOf, OptionQuery>; + + /// All unapplied slashes that are queued for later. + #[pallet::storage] + #[pallet::getter(fn unapplied_slashes)] + pub(crate) type UnappliedSlashes = StorageMap< _, Twox64Concat, - T::AccountId, - Nominator, - OptionQuery, + SessionIndex, + BoundedVec< + slashing::UnappliedSlash, + T::MaxUnAppliedSlash, + >, + ValueQuery, >; - /// The pool of validator validators, each with their total backing stake - #[pallet::storage] - #[pallet::getter(fn validator_pool)] - pub(crate) type ValidatorPool = - StorageValue<_, OrderedSet>, T::DefaultStakingMaxValidators>, ValueQuery>; - /// A mapping of still-bonded sessions #[pallet::storage] #[pallet::getter(fn bonded_sessions)] pub(crate) type BondedSessions = StorageValue<_, BoundedVec, ValueQuery>; + #[pallet::genesis_config] + pub struct GenesisConfig { + pub stakers: Vec<(T::AccountId, Option, BalanceOf)>, + pub invulnerables: Vec, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { + stakers: Default::default(), + invulnerables: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + log::trace!("GenesisBuild:[{:#?}] - Entry!!!", line!()); + + let duplicate_invulnerables = self.invulnerables.iter().collect::>(); + assert!( + duplicate_invulnerables.len() == self.invulnerables.len(), + "duplicate invulnerables in genesis." + ); + + let invulnerables: StakingInvulnerables = + self.invulnerables.clone().try_into().expect("Too long invulnerables"); + + >::put(invulnerables); + + // Ensure balance is >= ED + let imbalance = T::Currency::issue(T::Currency::minimum_balance()); + T::Currency::resolve_creating(&T::PalletId::get().into_account(), imbalance); + + // Set collator commission to default config + >::put(T::DefaultValidatorFee::get()); + // Set total selected validators to minimum config + >::put(T::MinSelectedValidators::get()); + // Set default slash reward fraction + >::put(T::DefaultSlashRewardProportion::get()); + // Maximum Validators allowed to join the validators pool + >::put(T::DefaultStakingMaxValidators::get()); + // Minimum stake required for any account to be in `SelectedCandidates` for the session + >::put(T::DefaultStakingMinStakeSessionSelection::get()); + // Minimum stake required for any account to be a validator candidate + >::put(T::DefaultStakingMinValidatorBond::get()); + // Set default min nomination stake value + >::put(T::DefaultStakingMinNominationChillThreshold::get()); + // Staking config minimum nominator total bond + >::put(T::DefaultStakingMinNominatorTotalBond::get()); + + log::trace!( + "GenesisBuild:[{:#?}] - Staking Cfg ([{:#?}],[{:#?}],[{:#?}],[{:#?}],[{:#?}])", + line!(), + >::get(), + >::get(), + >::get(), + >::get(), + >::get(), + ); + + for &(ref actor, ref opt_val, balance) in &self.stakers { + assert!( + T::Currency::free_balance(actor) >= balance, + "Account does not have enough balance to bond." + ); + + let _ = if let Some(nominated_val) = opt_val { + >::nominator_nominate( + T::Origin::from(Some(actor.clone()).into()), + nominated_val.clone(), + balance, + false, + ) + } else { + >::validator_join_pool(T::Origin::from(Some(actor.clone()).into()), balance) + }; + } + + let genesis_session_idx = 0u32; + + // Choose top TotalSelected validators + let (v_count, total_staked) = match >::select_session_validators(genesis_session_idx) { + Ok((v_count, total_staked)) => (v_count, total_staked), + Err(_) => { + log::error!("GenesisBuild:[{:#?}] - Select Session Validator Failure", line!(),); + (Zero::zero(), Zero::zero()) + } + }; + + // Start Session 1 + >::put(genesis_session_idx); + // Snapshot total stake + >::insert(genesis_session_idx, >::get()); + + log::trace!( + "GenesisBuild:[{:#?}] - (SI[{}],VC[{}],TS[{:#?}])", + line!(), + genesis_session_idx, + v_count, + total_staked, + ); + + >::deposit_event(Event::NewSession( + T::BlockNumber::zero(), + genesis_session_idx, + v_count, + total_staked, + )); + } + } + impl Pallet { pub(crate) fn is_validator(acc: &T::AccountId) -> bool { >::get(acc).is_some() } + pub(crate) fn is_nominator(acc: &T::AccountId) -> bool { >::get(acc).is_some() } + // ensure validator is active before calling - pub fn update_validators_pool(validator: T::AccountId, total: BalanceOf) { + pub fn update_validators_pool(validator: &T::AccountId, total: BalanceOf) -> Result<(), ()> { log::trace!( "update_validators_pool:[{:#?}] | Own[{:#?}] | Tot[{:#?}]", line!(), validator, total, ); - >::mutate(|validators| { - validators.remove(&Bond::from_owner(validator.clone())); + >::try_mutate(|validators| -> Result<(), ()> { + validators.remove(&Bond::from_owner(validator.clone()))?; validators.insert(Bond { - owner: validator, + owner: validator.clone(), amount: total, - }); - }); + })?; + Ok(()) + }) + } + + // ensure validator is active before calling + pub fn remove_from_validators_pool(validator: &T::AccountId) -> Result<(), ()> { + log::trace!("remove_from_validators_pool:[{:#?}] | Own[{:#?}]", line!(), validator); + >::try_mutate(|validators| -> Result<(), ()> { + validators.remove(&Bond::from_owner(validator.clone()))?; + Ok(()) + }) + } + + pub(crate) fn validator_deactivate(controller: &T::AccountId) -> Result<(), Error> { + log::trace!("validator_deactivate:[{:#?}] - Acc[{:#?}]", line!(), controller); + >::try_mutate(&controller, |maybe_validator| -> Result<(), Error> { + let valid_state = maybe_validator.as_mut().ok_or(>::ValidatorDNE)?; + valid_state.go_offline(); + Self::remove_from_validators_pool(&controller).map_err(|_| >::ValidatorDNE)?; + Ok(().into()) + }) + } + + fn nominator_leaves_validator( + nominator: &T::AccountId, + validator: &T::AccountId, + ) -> DispatchResultWithPostInfo { + >::try_mutate(validator, |maybe_validator| -> DispatchResultWithPostInfo { + let mut state = maybe_validator.as_mut().ok_or(>::ValidatorDNE)?; + let mut exists: Option> = None; + + let nominations_inner: Vec>> = state + .nominators + .get_inner() + .map_err(|_| >::OrderedSetFailure)?; + + let noms: Vec>> = nominations_inner + .into_iter() + .filter_map(|nom| { + if nom.owner != *nominator { + Some(nom) + } else { + exists = Some(nom.amount); + None + } + }) + .collect(); + let nominator_stake = exists.ok_or(>::ValidatorDNE)?; + let nominators = OrderedSet::try_from(noms).map_err(|_| >::OrderedSetFailure)?; + + state.nominators = nominators; + state.nomi_bond_total = state.nomi_bond_total.saturating_sub(nominator_stake); + state.total = state.total.saturating_sub(nominator_stake); + if state.is_active() { + Self::update_validators_pool(&validator, state.total) + .map_err(|_| >::ValidatorPoolOverflow)?; + } + Self::deposit_event(Event::NominatorLeftValidator( + nominator.clone(), + validator.clone(), + nominator_stake, + state.total, + )); + Ok(().into()) + })?; + Ok(().into()) } + fn nominator_revokes_validator( - acc: T::AccountId, - validator: T::AccountId, + acc: &T::AccountId, + validator: &T::AccountId, do_force: bool, ) -> DispatchResultWithPostInfo { - let mut nominator_state = >::get(&acc).ok_or(>::NominatorDNE)?; + let mut nominator_state = >::get(acc).ok_or(>::NominatorDNE)?; ensure!( (nominator_state.unlocking.len() as u32) < T::MaxChunkUnlock::get(), @@ -1015,7 +1439,7 @@ pub mod pallet { let old_active_bond = nominator_state.active_bond; - let remaining = nominator_state.rm_nomination(validator.clone(), false)?; + let remaining = nominator_state.rm_nomination(validator, false)?; if !do_force { ensure!( @@ -1024,7 +1448,7 @@ pub mod pallet { ); } - Self::nominator_leaves_validator(acc.clone(), validator)?; + Self::nominator_leaves_validator(&acc, validator)?; >::mutate(|x| *x = x.saturating_sub(old_active_bond.saturating_sub(remaining))); @@ -1040,48 +1464,482 @@ pub mod pallet { Ok(().into()) } - fn nominator_leaves_validator(nominator: T::AccountId, validator: T::AccountId) -> DispatchResultWithPostInfo { - >::try_mutate_exists( - validator.clone(), - |maybe_validator| -> DispatchResultWithPostInfo { - let mut state = maybe_validator.as_mut().ok_or(>::ValidatorDNE)?; - let mut exists: Option> = None; - - let nominations_inner: Vec>> = state - .nominators - .get_inner() - .map_err(|_| >::OrderedSetFailure)?; - - let noms: Vec>> = nominations_inner - .into_iter() - .filter_map(|nom| { - if nom.owner != nominator { - Some(nom) - } else { - exists = Some(nom.amount); - None + + fn validator_revokes_nomination( + nominator_acc: &T::AccountId, + validator: &T::AccountId, + ) -> DispatchResultWithPostInfo { + >::try_mutate(nominator_acc, |maybe_nominator_state| -> DispatchResultWithPostInfo { + let nominator_state = maybe_nominator_state.as_mut().ok_or(>::NominatorDNE)?; + + let old_active_bond = nominator_state.active_bond; + + let _remaining = nominator_state.rm_nomination(validator, false)?; + + nominator_state + .unlocking + .try_push(UnlockChunk { + value: old_active_bond.saturating_sub(nominator_state.active_bond), + session_idx: Self::active_session(), + }) + .map_err(|_| >::UnlockOverFlowError)?; + + Self::deposit_event(Event::NominatorLeftValidator( + nominator_acc.clone(), + validator.clone(), + old_active_bond.saturating_sub(nominator_state.active_bond), + Zero::zero(), + )); + + Ok(().into()) + })?; + Ok(().into()) + } + + fn validator_freeze_nomination( + nominator_acc: &T::AccountId, + validator: &T::AccountId, + ) -> DispatchResultWithPostInfo { + >::try_mutate(&nominator_acc, |maybe_nominator_state| -> DispatchResultWithPostInfo { + if let Some(nominator_state) = maybe_nominator_state.as_mut() { + let old_active_bond = nominator_state.active_bond; + + let _remaining = nominator_state.rm_nomination(&validator, true)?; + Self::deposit_event(Event::NominationBelowThreashold( + nominator_acc.clone(), + validator.clone(), + old_active_bond.saturating_sub(nominator_state.active_bond), + nominator_state.frozen_bond, + nominator_state.active_bond, + )); + } + Ok(().into()) + }) + } + + pub(crate) fn pay_stakers(next: SessionIndex) { + log::trace!("pay_stakers:[{:#?}] - Sess-idx[{:#?}]", line!(), next); + + let mint = |amt: BalanceOf, to: T::AccountId| -> DispatchResultWithPostInfo { + if amt > T::Currency::minimum_balance() { + >::try_mutate(&to, |rewards| -> DispatchResultWithPostInfo { + rewards + .try_push(StakeReward { + session_idx: next, + value: amt, + }) + .map_err(|_| { + log::error!("pay_stakers:[{:#?}] - StakeRewards Overflow Error", line!(),); + >::StakeRewardOverflow + })?; + Self::deposit_event(Event::StakeReward(to.clone(), amt)); + Ok(().into()) + })?; + } + Ok(().into()) + }; + + let validator_fee = >::get(); + let total = >::get(next); + // let total_staked = >::get(next); + // let issuance = Self::compute_issuance(total_staked); + let issuance = Self::session_validator_reward(next); + for (val, pts) in >::iter_prefix(next) { + let pct_due = Perbill::from_rational(pts, total); + let mut amt_due = pct_due * issuance; + + log::trace!( + "pay_stakers:[{:#?}] - L1 [{:#?}] | [{:#?}] | [{:#?}]", + line!(), + total, + issuance, + pct_due + ); + + log::trace!( + "pay_stakers:[{:#?}] - L2 [{:#?}] | [{:#?}] | [{:#?}]", + line!(), + val, + pts, + amt_due + ); + + if amt_due <= T::Currency::minimum_balance() { + continue; + } + // Take the snapshot of block author and nominations + // let state = >::take(next, &val); + let state = Self::at_stake(next, &val); + + if state.nominators.is_empty() { + // solo collator with no nominators + match mint(amt_due, val.clone()) { + Ok(_) => { + log::trace!("pay_stakers:[{:#?}] - L3 Solo Mode", line!()); + } + Err(err) => { + log::error!("pay_stakers:[{:#?}] - Mint Failure:[{:#?}]", line!(), err); + } + } + } else { + let val_pct = Perbill::from_rational(state.bond, state.total); + let commission = validator_fee * amt_due; + let val_due = if commission > T::Currency::minimum_balance() { + amt_due = amt_due.saturating_sub(commission); + (val_pct * amt_due).saturating_add(commission) + } else { + // commission is negligible so not applied + val_pct * amt_due + }; + + log::trace!( + "pay_stakers:[{:#?}] - L4 [{:#?}] | [{:#?}] | [{:#?}]", + line!(), + validator_fee, + val_due, + amt_due, + ); + + match mint(val_due, val.clone()) { + Ok(_) => { + log::trace!("pay_stakers:[{:#?}] - L3 Solo Mode", line!()); + } + Err(err) => { + log::error!("pay_stakers:[{:#?}] - Mint Failure:[{:#?}]", line!(), err); + } + } + + // pay nominators due portion + for Bond { owner, amount } in state.nominators { + let percent = Perbill::from_rational(amount, state.total); + let due = percent * amt_due; + match mint(due, owner) { + Ok(_) => (), + Err(err) => { + log::error!("pay_stakers:[{:#?}] - Mint Failure:[{:#?}]", line!(), err); } - }) - .collect(); - let nominator_stake = exists.ok_or(>::ValidatorDNE)?; - let nominators = OrderedSet::try_from(noms).map_err(|_| >::OrderedSetFailure)?; - - state.nominators = nominators; - state.nomi_bond_total = state.nomi_bond_total.saturating_sub(nominator_stake); - state.total = state.total.saturating_sub(nominator_stake); - if state.is_active() { - Self::update_validators_pool(validator.clone(), state.total); + } + } + } + } + } + + pub(crate) fn execute_delayed_validator_exits(next: SessionIndex) { + let remain_exits = >::get() + .0 + .into_iter() + .filter_map(|x| { + if x.amount > next { + Some(x) + } else { + if let Some(state) = >::get(&x.owner) { + // revoke all nominations + let nominators: Vec>> = match state.nominators.get_inner() { + Ok(nominators) => nominators, + Err(_) => { + log::error!( + "execute_delayed_validator_exits:[{:#?}] - OrderedSet Failure", + line!(), + ); + return None; + } + }; + + for bond in nominators { + match Self::validator_revokes_nomination(&bond.owner, &x.owner) { + Ok(_) => (), + Err(_) => { + log::error!( + "execute_delayed_validator_exits:[{:#?}] - validator_revokes_nomination Failure", + line!(), + ); + } + } + } + // return stake to validator + let mut unlock_chunk_total: BalanceOf = Zero::zero(); + let _ = state.unlocking.clone().to_vec().iter().map(|chunk| { + unlock_chunk_total = unlock_chunk_total.saturating_add(chunk.value); + }); + + let new_total = + >::get().saturating_sub(state.total.saturating_sub(unlock_chunk_total)); + >::put(new_total); + + T::Currency::remove_lock(T::StakingLockId::get(), &x.owner); + + let _ = Self::kill_state_info(&x.owner); + + Self::deposit_event(Event::ValidatorLeft(x.owner, state.total, new_total)); + } + None + } + }) + .collect::>>(); + + let updated_exist_list: OrderedSet, T::DefaultStakingMaxValidators> = + match OrderedSet::try_from(remain_exits) { + Ok(updated_exist_list) => updated_exist_list, + Err(_) => { + log::error!( + "execute_delayed_validator_exits:[{:#?}] - OrderedSet Failure, Could be Overflow", + line!(), + ); + return (); } - Self::deposit_event(Event::NominatorLeftValidator( - nominator, - validator, - nominator_stake, - state.total, + }; + + >::put(updated_exist_list); + } + + fn active_stake_reconciliation() { + let validators: Vec>> = match >::get().get_inner() { + Ok(validators) => validators, + Err(_) => { + log::error!("active_stake_reconciliation:[{:#?}] - OrderedSet Failure", line!(),); + return (); + } + }; + + let reconciled_list = validators + .into_iter() + .filter_map(|x| { + let bond_status = if let Some(valid_state) = >::get(&x.owner) { + if valid_state.is_active() && valid_state.bond > Self::staking_min_validator_bond() { + Some(Bond { + owner: x.owner.clone(), + amount: valid_state.total, + }) + } else { + None + } + } else { + None + }; + + match Self::validator_stake_reconciliation(&x.owner) { + Err(_) => { + log::error!( + "active_stake_reconciliation:[{:#?}] - Reconciliation failure for Validator[{:#?}]", + line!(), + &x.owner + ); + None + } + Ok(_) => bond_status, + } + }) + .collect::>>>(); + + let reconciled_updated = match OrderedSet::try_from(reconciled_list) { + Ok(reconciled_updated) => reconciled_updated, + Err(_) => { + log::error!( + "active_stake_reconciliation:[{:#?}] - OrderedSet Failure Could be Overflow", + line!(), + ); + return (); + } + }; + + >::put(reconciled_updated); + } + + pub(crate) fn validator_stake_reconciliation(controller: &T::AccountId) -> Result<(), Error> { + >::try_mutate(&controller, |maybe_validator| -> Result<(), Error> { + let mut valid_state = maybe_validator.as_mut().ok_or(>::ValidatorDNE)?; + + let nomination = valid_state + .clone() + .nominators + .get_inner() + .map_err(|_| >::OrderedSetFailure)?; + + let noms: Vec>> = nomination + .into_iter() + .filter_map(|nom| { + if nom.amount < Self::staking_min_nomination_chill_threshold() { + match Self::validator_freeze_nomination(&nom.owner, &controller) { + Ok(_) => (), + Err(_) => { + log::error!( + "validator_stake_reconciliation:[{:#?}] - validator_freeze_nomination Failure", + line!(), + ); + } + } + + match valid_state.dec_nominator(&nom.owner, nom.amount) { + Ok(_) => (), + Err(_) => { + log::error!( + "validator_stake_reconciliation:[{:#?}] - dec_nominator Failure", + line!(), + ); + } + } + + None + } else { + Some(nom) + } + }) + .collect(); + + let nominators = OrderedSet::try_from(noms).map_err(|_| >::OrderedSetFailure)?; + valid_state.nominators = nominators; + + if valid_state.bond < Self::staking_min_validator_bond() && valid_state.is_active() { + valid_state.go_offline(); + Self::deposit_event(Event::ValidatorBondBelowThreashold( + controller.clone(), + valid_state.bond, + valid_state.total, )); - Ok(().into()) - }, - )?; - Ok(().into()) + } + + Ok(().into()) + }) + } + + /// Best as in most cumulatively supported in terms of stake + pub(crate) fn select_session_validators(next: SessionIndex) -> Result<(u32, BalanceOf), ()> { + let (mut validators_count, mut total) = (0u32, >::zero()); + + let mut validators = match >::get().get_inner() { + Ok(validators) => validators, + Err(_) => { + log::error!("select_session_validators:[{:#?}] - OrderedSet Failure", line!(),); + return Err(()); + } + }; + + // order validators pool by stake (least to greatest so requires `rev()`) + validators.sort_unstable_by(|a, b| a.amount.partial_cmp(&b.amount).unwrap()); + let top_n = >::get() as usize; + // choose the top TotalSelected qualified validators, ordered by stake + let mut top_validators = validators + .into_iter() + .rev() + .take(top_n) + .filter(|x| x.amount >= >::get()) + .filter(|x| T::ValidatorRegistration::is_registered(&x.owner)) + .map(|x| x.owner) + .collect::>(); + + if !>::get().to_vec().is_empty() { + top_validators = Self::invulnerables() + .to_vec() + .iter() + .chain(top_validators.iter()) + .cloned() + .collect::>(); + } + + top_validators.sort(); + top_validators.dedup(); + + // Overflow should not occur, since size is reserved for DefaultStakingMaxValidators + let selected_validators: BoundedVec = + top_validators.clone().try_into().expect("Selected Valiadtors Overflow"); + + // snapshot exposure for round for weighting reward distribution + for account in top_validators.iter() { + let state = >::get(&account).expect("all members of ValidatorQ must be validators"); + let amount = state.bond.saturating_add(state.nomi_bond_total); + let exposure: ValidatorSnapshot = + state.build_snapshot().expect("Validator Snapshot Build Failure"); + >::insert(next, account, exposure); + validators_count = validators_count.saturating_add(1u32); + total = total.saturating_add(amount); + Self::deposit_event(Event::ValidatorChosen(next, account.clone(), amount)); + } + + // top_validators.sort(); + // insert canonical collator set + >::put(selected_validators); + Ok((validators_count, total)) + } + /// Add reward points to validators using their account ID. + /// + /// Validators are keyed by stash account ID and must be in the current elected set. + /// + /// For each element in the iterator the given number of points in u32 is added to the + /// validator, thus duplicates are handled. + /// + /// 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`. + pub(crate) fn reward_by_ids(validators_points: impl IntoIterator) { + let now = Self::active_session(); + for (validator, points) in validators_points.into_iter() { + if Self::is_validator(&validator) { + let score_points = >::get(now, &validator).saturating_add(points); + >::insert(now, validator, score_points); + >::mutate(now, |x| *x = x.saturating_add(points)); + } + } + } + /// Remove all associated data of a stash account from the staking system. + /// + /// Assumes storage is upgraded before calling. + /// + /// This is called: + /// - after a `withdraw_unbonded()` call that frees all of a stash's bonded balance. + /// - through `reap_stash()` if the balance has fallen to zero (through slashing). + fn kill_state_info(controller: &T::AccountId) -> DispatchResult { + slashing::clear_slash_metadata::(controller)?; + if Self::is_validator(controller) { + >::remove(controller); + } else if Self::is_nominator(controller) { + >::remove(controller); + } + Ok(()) + } + /// Clear session information for given session index + pub(crate) fn clear_session_information(session_idx: SessionIndex) { + log::trace!( + "clear_session_information:[{:#?}] - AccuBal[{:#?}]", + line!(), + >::get(session_idx), + ); + + >::remove(session_idx); + >::remove_prefix(session_idx, None); + >::remove(session_idx); + >::remove_prefix(session_idx, None); + >::remove(session_idx); + >::remove(session_idx); + slashing::clear_session_metadata::(session_idx); + + // withdraw rewards + match T::Currency::withdraw( + &T::PalletId::get().into_account(), + >::take(session_idx), + WithdrawReasons::all(), + ExistenceRequirement::KeepAlive, + ) { + Ok(imbalance) => T::RewardRemainder::on_unbalanced(imbalance), + Err(err) => { + log::error!( + "clear_session_information:[{:#?}] - [{:#?}] | [{:#?}]", + line!(), + err, + "Warning: an error happened when trying to handle active session rewards \ + remainder", + ); + } + }; + } + /// Apply previously-unapplied slashes on the beginning of a new session, after a delay. + pub(crate) fn apply_unapplied_slashes(active_session: SessionIndex) { + if >::contains_key(active_session) { + let session_slashes = >::take(&active_session); + for mut unapplied_slash in session_slashes { + unapplied_slash.apply_slash(); + } + } } } } diff --git a/pallets/staking/src/mock.rs b/pallets/staking/src/mock.rs index 4dbac487e30..4af7426ba93 100644 --- a/pallets/staking/src/mock.rs +++ b/pallets/staking/src/mock.rs @@ -15,20 +15,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#![cfg(test)] + use super::*; use crate as nodle_staking; -use crate::hooks; + use frame_support::{ - assert_ok, ord_parameter_types, parameter_types, - traits::{ - Currency, FindAuthor, Imbalance, LockIdentifier, OnFinalize, OnInitialize, OnUnbalanced, OneSessionHandler, - }, + assert_noop, assert_ok, ord_parameter_types, + pallet_prelude::*, + parameter_types, + traits::{ConstU32, Currency, FindAuthor, Imbalance, LockIdentifier, OnUnbalanced, OneSessionHandler}, weights::constants::RocksDbWeight, PalletId, }; use frame_system::EnsureSignedBy; use sp_core::H256; - use sp_runtime::{ testing::{Header, UintAuthorityId}, traits::{IdentityLookup, Zero}, @@ -101,7 +102,6 @@ frame_support::construct_runtime!( Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, NodleStaking: nodle_staking::{Pallet, Call, Config, Storage, Event}, - Poa: pallet_poa::{Pallet, Storage}, Session: pallet_session::{Pallet, Call, Storage, Event, Config}, Historical: pallet_session::historical::{Pallet, Storage}, } @@ -200,9 +200,10 @@ impl pallet_session::Config for Test { type WeightInfo = (); } impl pallet_session::historical::Config for Test { - type FullIdentification = crate::types::ValidatorSnapshot; - type FullIdentificationOf = crate::types::ValidatorSnapshotOf; + type FullIdentification = crate::types::ValidatorSnapshot; + type FullIdentificationOf = crate::types::ValidatorSnapshotOf; } + impl pallet_authorship::Config for Test { type FindAuthor = Author11; type UncleGenerations = UncleGenerations; @@ -210,8 +211,6 @@ impl pallet_authorship::Config for Test { type EventHandler = Pallet; } -impl pallet_poa::Config for Test {} - parameter_types! { pub const MinimumPeriod: u64 = 5; } @@ -225,18 +224,14 @@ ord_parameter_types! { pub const CancelOrigin: AccountId = 42; } parameter_types! { - pub const MinSelectedValidators: u32 = 5; pub const MaxNominatorsPerValidator: u32 = 4; - pub const MaxValidatorPerNominator: u32 = 4; pub const DefaultValidatorFee: Perbill = Perbill::from_percent(20); pub const DefaultSlashRewardProportion: Perbill = Perbill::from_percent(10); pub const DefaultSlashRewardFraction: Perbill = Perbill::from_percent(50); - pub const DefaultStakingMaxValidators: u32 = 50; pub const DefaultStakingMinStakeSessionSelection: Balance = 10; pub const DefaultStakingMinValidatorBond: Balance = 10; pub const DefaultStakingMinNominatorTotalBond: Balance = 5; pub const DefaultStakingMinNominationChillThreshold: Balance = 3; - pub const MaxChunkUnlock: usize = 32; pub const StakingPalletId: PalletId = PalletId(*b"mockstak"); pub const StakingLockId: LockIdentifier = *b"staking "; } @@ -244,19 +239,23 @@ impl Config for Test { type Event = Event; type Currency = Balances; type BondedDuration = BondedDuration; - type MinSelectedValidators = MinSelectedValidators; + type MinSelectedValidators = ConstU32<5>; + type MaxInvulnerableStakers = ConstU32<5>; type MaxNominatorsPerValidator = MaxNominatorsPerValidator; - type MaxValidatorPerNominator = MaxValidatorPerNominator; + type MaxValidatorPerNominator = ConstU32<4>; + type MaxSlashReporters = ConstU32<4>; type DefaultValidatorFee = DefaultValidatorFee; type DefaultSlashRewardProportion = DefaultSlashRewardProportion; type DefaultSlashRewardFraction = DefaultSlashRewardFraction; - type DefaultStakingMaxValidators = DefaultStakingMaxValidators; + type DefaultStakingMaxValidators = ConstU32<50>; type DefaultStakingMinStakeSessionSelection = DefaultStakingMinStakeSessionSelection; type DefaultStakingMinValidatorBond = DefaultStakingMinValidatorBond; type DefaultStakingMinNominatorTotalBond = DefaultStakingMinNominatorTotalBond; type DefaultStakingMinNominationChillThreshold = DefaultStakingMinNominationChillThreshold; type RewardRemainder = RewardRemainderMock; - type MaxChunkUnlock = MaxChunkUnlock; + type MaxChunkUnlock = ConstU32<32>; + type MaxUnAppliedSlash = ConstU32<32>; + type MaxSlashSpan = ConstU32<64>; type PalletId = StakingPalletId; type StakingLockId = StakingLockId; type Slash = (); @@ -613,11 +612,14 @@ pub(crate) fn bond_nominator(ctrl: AccountId, val: Balance, target: AccountId) { } pub(crate) fn validators_in_pool() -> Vec { - NodleStaking::validator_pool().0.into_iter().map(|s| s.owner).collect() + let validators = NodleStaking::validator_pool() + .get_inner() + .expect("Orderset Decode Error"); + validators.into_iter().map(|s| s.owner).collect() } pub(crate) fn selected_validators() -> Vec { - NodleStaking::selected_validators() + NodleStaking::selected_validators().to_vec() } pub(crate) fn on_offence_now( @@ -635,7 +637,7 @@ pub(crate) fn on_offence_in_session( session_idx: SessionIndex, disable_strategy: DisableStrategy, ) { - let bonded_session = NodleStaking::bonded_sessions(); + let bonded_session = NodleStaking::bonded_sessions().to_vec(); for bond_session in bonded_session.iter() { if *bond_session == session_idx { let _ = NodleStaking::on_offence(offenders, slash_fraction, session_idx, disable_strategy); diff --git a/pallets/staking/src/set.rs b/pallets/staking/src/set.rs index 771cbb0a00d..5eedbcf0bed 100644 --- a/pallets/staking/src/set.rs +++ b/pallets/staking/src/set.rs @@ -15,15 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -use codec::{Codec, Decode, Encode}; +use codec::{Decode, Encode}; use derivative::Derivative; -use frame_support::{bounded_vec, pallet_prelude::MaxEncodedLen, traits::Get, BoundedVec}; +use frame_support::{pallet_prelude::MaxEncodedLen, traits::Get, BoundedVec}; use scale_info::TypeInfo; -use sp_std::{cmp::Ordering, fmt::Debug, marker::PhantomData, prelude::*}; +use sp_std::{fmt::Debug, prelude::*}; -// NOTE: use `Derivative` here until is fixed. -// The problem is that `#[derive(Clone)]` requires `Clone` for ALL its generic types, -// which does not make sense for `Get<>`. #[derive(Derivative)] #[derivative( Debug(bound = "T: Debug"), @@ -33,49 +30,11 @@ use sp_std::{cmp::Ordering, fmt::Debug, marker::PhantomData, prelude::*}; )] #[derive(Decode, Encode, TypeInfo)] #[scale_info(skip_type_params(S))] -pub struct BoundedCodecWrapper>(pub BoundedVec, PhantomData); - -impl MaxEncodedLen for BoundedCodecWrapper -where - S: Get, -{ - fn max_encoded_len() -> usize { - S::get() as usize - } -} - -impl BoundedCodecWrapper -where - T: Encode + Decode, - S: Get, -{ - /// Creates a new `Self` from the given `T`. - // - // NOTE: No `TryFrom/TryInto` possible until . - pub fn try_from(inner: T) -> Result { - let encoded: BoundedVec = inner.encode().try_into().map_err(|_| ())?; - Ok(Self(encoded, PhantomData::::default())) - } - - /// Returns the wrapped `CallOrHashOf`. - pub fn try_into_inner(self) -> Result { - Decode::decode(&mut &self.0[..]).map_err(|_| ()) - } -} - -#[derive(Derivative)] -#[derivative( - Debug(bound = "T: Debug"), - Clone(bound = "T: Clone"), - PartialEq(bound = "T: PartialEq"), - Eq(bound = "T: Eq") -)] -#[derive(Decode, Encode, TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct OrderedSet>(pub BoundedCodecWrapper, S>); +pub struct OrderedSet>(pub BoundedVec); impl MaxEncodedLen for OrderedSet where + T: Encode + Decode, S: Get, { fn max_encoded_len() -> usize { @@ -91,7 +50,7 @@ where /// Create a default empty set fn default() -> Self { let inner: Vec = Vec::with_capacity(S::get() as usize); - Self(BoundedCodecWrapper::try_from(inner).expect("OrderedSet Failed To Create Default")) + Self(BoundedVec::try_from(inner).expect("OrderedSet Failed To Create Default")) } } @@ -116,7 +75,7 @@ where /// Create a set from a `Vec`. /// Assume `v` is sorted and contain unique elements. pub fn from_sorted_set(val: Vec) -> Result { - let inner = BoundedCodecWrapper::try_from(val).map_err(|_| { + let inner = BoundedVec::try_from(val).map_err(|_| { log::error!("OrderedSet Failed To Create From Vec"); () })?; @@ -124,17 +83,17 @@ where } pub fn get_inner(&self) -> Result, ()> { - let inner = self.0.clone().try_into_inner().map_err(|_| ())?; + let inner = self.0.clone().to_vec(); Ok(inner.clone()) } pub fn len(&self) -> Result { - let inner = self.0.clone().try_into_inner().map_err(|_| ())?; + let inner = self.0.clone().to_vec(); Ok(inner.len()) } pub fn update_inner(&mut self, inner: Vec) -> Result<(), ()> { - self.0 = BoundedCodecWrapper::try_from(inner).map_err(|_| { + self.0 = BoundedVec::try_from(inner).map_err(|_| { log::error!("Orderset Failed to Update inner"); () })?; @@ -144,16 +103,13 @@ where /// Insert an element. /// Return true if insertion happened. pub fn insert(&mut self, value: T) -> Result { - let mut inner = self.0.clone().try_into_inner().map_err(|_| { - log::error!("Orderset Failed to Decode"); - () - })?; + let mut inner = self.0.clone().to_vec(); match inner.binary_search(&value) { Ok(_) => Ok(false), Err(loc) => { inner.insert(loc, value); - self.0 = BoundedCodecWrapper::try_from(inner).map_err(|_| { + self.0 = BoundedVec::try_from(inner).map_err(|_| { log::error!("Orderset Failed to Insert"); () })?; @@ -165,15 +121,12 @@ where /// Remove an element. /// Return true if removal happened. pub fn remove(&mut self, value: &T) -> Result { - let mut inner = self.0.clone().try_into_inner().map_err(|_| { - log::error!("Orderset Failed to Decode"); - () - })?; + let mut inner = self.0.clone().to_vec(); match inner.binary_search(&value) { Ok(loc) => { inner.remove(loc); - self.0 = BoundedCodecWrapper::try_from(inner).map_err(|_| { + self.0 = BoundedVec::try_from(inner).map_err(|_| { log::error!("Orderset Failed to Remove"); () })?; @@ -185,10 +138,7 @@ where /// Return if the set contains `value` pub fn contains(&self, value: &T) -> Result { - let inner = self.0.clone().try_into_inner().map_err(|_| { - log::error!("Orderset Failed to Decode"); - () - })?; + let inner = self.0.clone().to_vec(); match inner.binary_search(&value) { Ok(loc) => Ok(loc), @@ -198,13 +148,10 @@ where /// Clear the set pub fn clear(&mut self) -> Result<(), ()> { - let mut inner = self.0.clone().try_into_inner().map_err(|_| { - log::error!("Orderset Failed to Decode"); - () - })?; + let mut inner = self.0.clone().to_vec(); inner.clear(); - self.0 = BoundedCodecWrapper::try_from(inner).map_err(|_| { + self.0 = BoundedVec::try_from(inner).map_err(|_| { log::error!("Orderset Failed to Remove"); () })?; diff --git a/pallets/staking/src/slashing.rs b/pallets/staking/src/slashing.rs index 4a5ce7d95bf..8f0367284fc 100644 --- a/pallets/staking/src/slashing.rs +++ b/pallets/staking/src/slashing.rs @@ -16,25 +16,37 @@ * along with this program. If not, see . */ -use super::{BalanceOf, Config, Event, NegativeImbalanceOf, Pallet, Store}; +use super::{BalanceOf, Config, Error, Event, NegativeImbalanceOf, Pallet, Store}; use crate::hooks::SessionInterface; -use crate::types::{SpanIndex, UnappliedSlash, ValidatorSnapshot}; +use crate::types::{SpanIndex, ValidatorSnapshot}; use codec::{Decode, Encode}; -use frame_support::traits::{Currency, Get, Imbalance, LockableCurrency, OnUnbalanced, WithdrawReasons}; +use derivative::Derivative; +use frame_support::{ + bounded_vec, + pallet_prelude::MaxEncodedLen, + traits::{Currency, Get, Imbalance, LockableCurrency, OnUnbalanced, WithdrawReasons}, + BoundedVec, +}; +use scale_info::TypeInfo; use sp_runtime::{ traits::{Saturating, Zero}, - DispatchResult, Perbill, RuntimeDebug, + DispatchResult, Perbill, }; use sp_staking::{offence::DisableStrategy, SessionIndex}; -use sp_std::{cmp::Ordering, vec::Vec}; +use sp_std::{ + cmp::{max, Ordering}, + fmt::Debug, + marker::PhantomData, + vec::Vec, +}; // A range of start..end eras for a slashing span. -#[derive(Encode, Decode, scale_info::TypeInfo)] +#[derive(Clone, Encode, Decode, TypeInfo)] #[cfg_attr(test, derive(Debug, PartialEq))] pub(crate) struct SlashingSpan { - pub(crate) index: SpanIndex, - pub(crate) start: SessionIndex, - pub(crate) length: Option, // the ongoing slashing span has indeterminate length. + index: SpanIndex, + start: SessionIndex, + length: Option, // the ongoing slashing span has indeterminate length. } impl SlashingSpan { @@ -44,8 +56,11 @@ impl SlashingSpan { } /// An encoding of all of a nominator's slashing spans. -#[derive(Encode, Decode, RuntimeDebug, scale_info::TypeInfo)] -pub struct SlashingSpans { +#[derive(Derivative)] +#[derivative(Debug(bound = "T: Debug"), Clone(bound = "T: Clone"))] +#[derive(Decode, Encode, TypeInfo)] +#[scale_info(skip_type_params(T, S))] +pub struct SlashingSpans> { // the index of the current slashing span of the nominator. different for // every controller, resets when the account hits free balance 0. span_index: SpanIndex, @@ -55,13 +70,31 @@ pub struct SlashingSpans { last_nonzero_slash: SessionIndex, // all prior slashing spans' start indices, in reverse order (most recent first) // encoded as offsets relative to the slashing span after it. - prior: Vec, + prior: BoundedVec, + // dummy marker field + _marker: PhantomData, +} + +impl MaxEncodedLen for SlashingSpans +where + T: Config, + S: Get, +{ + fn max_encoded_len() -> usize { + S::get() as usize + } } -impl SlashingSpans { +impl SlashingSpans +where + T: Config, + S: Get, +{ // creates a new record of slashing spans for a controller, starting at the beginning // of the bonding period, relative to now. pub(crate) fn new(window_start: SessionIndex) -> Self { + let inner: Vec = Vec::with_capacity(S::get() as usize); + let prior_default = BoundedVec::try_from(inner).expect("SlashingSpans Failed To Create Default"); SlashingSpans { span_index: 0, last_start: window_start, @@ -69,23 +102,27 @@ impl SlashingSpans { // the first slash is applied. setting equal to `window_start` would // put a time limit on nominations. last_nonzero_slash: 0, - prior: Vec::new(), + prior: prior_default, + _marker: >::default(), } } // update the slashing spans to reflect the start of a new span at the era after `now` // returns `true` if a new span was started, `false` otherwise. `false` indicates // that internal state is unchanged. - pub(crate) fn end_span(&mut self, now: SessionIndex) -> bool { + pub(crate) fn end_span(&mut self, now: SessionIndex) -> Result { let next_start = now.saturating_add(1); if next_start <= self.last_start { - return false; + return Ok(false); } let last_length = next_start.saturating_sub(self.last_start); - self.prior.insert(0, last_length); + self.prior.try_insert(0, last_length).map_err(|_| { + log::error!("Slashing Spans Overflow Error"); + () + })?; self.last_start = next_start; self.span_index = self.span_index.saturating_add(1); - true + Ok(true) } // an iterator over all slashing spans in _reverse_ order - most recent first. @@ -97,17 +134,23 @@ impl SlashingSpans { start: last_start, length: None, }; - let prior = self.prior.iter().cloned().map(move |length| { - let start = last_start - length; - last_start = start; - index = index.saturating_sub(1); - - SlashingSpan { - index, - start, - length: Some(length), - } - }); + + let span_prior: Vec = self.prior.clone().to_vec(); + + let prior: Vec = span_prior + .iter() + .map(move |length| { + let start = last_start - length; + last_start = start; + index = index.saturating_sub(1); + + SlashingSpan { + index, + start, + length: Some(*length), + } + }) + .collect(); sp_std::iter::once(last).chain(prior) } @@ -121,33 +164,40 @@ impl SlashingSpans { // // If this returns `Some`, then it includes a range start..end of all the span // indices which were pruned. - fn prune(&mut self, window_start: SessionIndex) -> Option<(SpanIndex, SpanIndex)> { + fn prune(&mut self, window_start: SessionIndex) -> Result, ()> { let old_idx = self .iter() .skip(1) // skip ongoing span. .position(|span| span.length.map_or(false, |len| span.start + len <= window_start)); - let earliest_span_index = self.span_index - self.prior.len() as SpanIndex; + let mut prior_span = self.prior.to_vec(); + + let earliest_span_index = self.span_index - prior_span.len() as SpanIndex; let pruned = match old_idx { Some(o) => { - self.prior.truncate(o); + prior_span.truncate(o); let new_earliest = self.span_index - self.prior.len() as SpanIndex; Some((earliest_span_index, new_earliest)) } None => None, }; + self.prior = BoundedVec::try_from(prior_span).map_err(|_| { + log::error!("Slasing Span prune failure"); + () + })?; + // readjust the ongoing span, if it started before the beginning of the window. self.last_start = sp_std::cmp::max(self.last_start, window_start); - pruned + Ok(pruned) } } /// A slashing-span record for a particular controller. -#[derive(Encode, Decode, Default, scale_info::TypeInfo)] +#[derive(Encode, Decode, Default, MaxEncodedLen, TypeInfo)] pub struct SpanRecord { - pub slashed: Balance, - pub paid_out: Balance, + slashed: Balance, + paid_out: Balance, } impl SpanRecord { @@ -158,215 +208,6 @@ impl SpanRecord { } } -/// Parameters for performing a slash. -#[derive(Clone)] -pub(crate) struct SlashParams<'a, T: 'a + Config> { - /// The controller account being slashed. - pub(crate) controller: &'a T::AccountId, - /// The proportion of the slash. - pub(crate) slash: Perbill, - /// The exposure of the controller and all nominators. - pub(crate) exposure: &'a ValidatorSnapshot>, - /// The session where the offence occurred. - pub(crate) slash_session: SessionIndex, - /// The first era in the current bonding period. - pub(crate) window_start: SessionIndex, - /// The current era. - pub(crate) now: SessionIndex, - /// The maximum percentage of a slash that ever gets paid out. - /// This is f_inf in the paper. - pub(crate) reward_proportion: Perbill, - /// When to disable offenders. - pub(crate) disable_strategy: DisableStrategy, -} - -/// Computes a slash of a validator and nominators. It returns an unapplied -/// record to be applied at some later point. Slashing metadata is updated in storage, -/// since unapplied records are only rarely intended to be dropped. -/// -/// The pending slash record returned does not have initialized reporters. Those have -/// to be set at a higher level, if any. -pub(crate) fn compute_slash(params: SlashParams) -> Option>> { - let SlashParams { - controller, - slash, - exposure, - slash_session, - window_start, - now, - reward_proportion, - disable_strategy, - } = params.clone(); - - log::trace!("compute_slash:[{:#?}] - Slash-[{:#?}]", line!(), slash); - - // is the slash amount here a maximum for the era? - let own_slash = slash * exposure.bond; - if slash * exposure.total == Zero::zero() { - log::trace!( - "compute_slash:[{:#?}] - ValidatorSS[ B-[{:#?}] | T-[{:#?}]] - Nop", - line!(), - exposure.bond, - exposure.total, - ); - // kick out the validator even if they won't be slashed, - // as long as the misbehavior is from their most recent slashing span. - kick_out_if_recent::(params); - return None; - } - - let (prior_slash_p, _era_slash) = as Store>::ValidatorSlashInSession::get(&slash_session, controller) - .unwrap_or((Perbill::zero(), Zero::zero())); - - // compare slash proportions rather than slash values to avoid issues due to rounding - // error. - if slash.deconstruct() > prior_slash_p.deconstruct() { - as Store>::ValidatorSlashInSession::insert(&slash_session, controller, &(slash, own_slash)); - } else { - // we slash based on the max in era - this new event is not the max, - // so neither the validator or any nominators will need an update. - // - // this does lead to a divergence of our system from the paper, which - // pays out some reward even if the latest report is not max-in-era. - // we opt to avoid the nominator lookups and edits and leave more rewards - // for more drastic misbehavior. - return None; - } - - // apply slash to validator. - let mut spans = fetch_spans::(controller, window_start, reward_proportion); - - let target_span = spans.compare_and_update_span_slash(slash_session, own_slash); - - if target_span == Some(spans.span_index()) { - // misbehavior occurred within the current slashing span - take appropriate - // actions. - - // chill the validator - it misbehaved in the current span and should - // not continue in the next election. also end the slashing span. - spans.end_span(now); - log::trace!( - "compute_slash:[{:#?}] - Call end_span() | SI[{:#?}] | @[{:#?}]", - line!(), - spans.span_index(), - now - ); - - if disable_strategy != DisableStrategy::Never { - >::validator_deactivate(params.controller); - - // make sure to disable validator till the end of this session - T::SessionInterface::disable_validator(params.controller); - } - } - - // apply slash to Nominator. - let mut nominators_slashed = Vec::new(); - spans.paid_out = - spans - .paid_out - .saturating_add(slash_nominators::(params, prior_slash_p, &mut nominators_slashed)); - - Some(UnappliedSlash { - validator: controller.clone(), - own: spans.slash_of, - others: nominators_slashed, - reporters: Vec::new(), - payout: spans.paid_out, - }) -} - -/// doesn't apply any slash, but kicks out the validator if the misbehavior is from -/// the most recent slashing span. -fn kick_out_if_recent(params: SlashParams) { - log::trace!("kick_out_if_recent:[{:#?}]", line!()); - - let mut spans = fetch_spans::(params.controller, params.window_start, params.reward_proportion); - - if spans.era_span(params.slash_session).map(|s| s.index) == Some(spans.span_index()) { - spans.end_span(params.now); - log::trace!( - "kick_out_if_recent:[{:#?}] - Call end_span() | SI[{:#?}] | @[{:#?}]", - line!(), - spans.span_index(), - params.now, - ); - - if params.disable_strategy == DisableStrategy::Always { - >::validator_deactivate(params.controller); - - // make sure to disable validator till the end of this session - T::SessionInterface::disable_validator(params.controller); - } - } -} - -/// Slash nominators. Accepts general parameters and the prior slash percentage of the validator. -/// -/// Returns the amount of reward to pay out. -fn slash_nominators( - params: SlashParams, - prior_slash_p: Perbill, - nominators_slashed: &mut Vec<(T::AccountId, BalanceOf)>, -) -> BalanceOf { - let SlashParams { - controller: _, - slash, - exposure, - slash_session, - window_start, - now, - reward_proportion, - disable_strategy: _, - } = params; - - let mut reward_payout: BalanceOf = Zero::zero(); - nominators_slashed.reserve(exposure.nominators.len()); - - for nominator in &exposure.nominators { - let controller = &nominator.owner; - - // the era slash of a nominator always grows, if the validator - // had a new max slash for the era. - let era_slash = { - let own_slash_prior = prior_slash_p * nominator.amount; - let own_slash_by_validator = slash * nominator.amount; - let own_slash_difference = own_slash_by_validator.saturating_sub(own_slash_prior); - - let mut era_slash = as Store>::NominatorSlashInSession::get(&slash_session, controller) - .unwrap_or_else(Zero::zero); - - era_slash = era_slash.saturating_add(own_slash_difference); - - as Store>::NominatorSlashInSession::insert(&slash_session, controller, &era_slash); - - era_slash - }; - - // compare the era slash against other eras in the same span. - let mut spans = fetch_spans::(controller, window_start, reward_proportion); - - let target_span = spans.compare_and_update_span_slash(slash_session, era_slash); - - if target_span == Some(spans.span_index()) { - // End the span, but don't chill the nominator. its nomination - // on this validator will be ignored in the future. - spans.end_span(now); - log::trace!( - "slash_nominators:[{:#?}] - Call end_span() | SI[{:#?}] | @[{:#?}]", - line!(), - spans.span_index(), - now - ); - } - - reward_payout = reward_payout.saturating_add(spans.paid_out); - nominators_slashed.push((controller.clone(), spans.slash_of)); - } - - reward_payout -} - /// helper struct for managing a set of spans we are currently inspecting. /// writes alterations to disk on drop, but only if a slash has been carried out. /// @@ -374,48 +215,27 @@ fn slash_nominators( /// dropping this struct applies any necessary slashes, which can lead to free balance /// being 0, and the account being garbage-collected -- a dead account should get no new /// metadata. -struct InspectingSpans<'a, T: Config + 'a> { +struct InspectingSpans { dirty: bool, window_start: SessionIndex, - controller: &'a T::AccountId, - spans: SlashingSpans, + controller: T::AccountId, + spans: SlashingSpans, paid_out: BalanceOf, slash_of: BalanceOf, reward_proportion: Perbill, _marker: sp_std::marker::PhantomData, } -/// fetches the slashing spans record for a controller account, initializing it if necessary. -fn fetch_spans<'a, T: Config + 'a>( - controller: &'a T::AccountId, - window_start: SessionIndex, - reward_proportion: Perbill, -) -> InspectingSpans<'a, T> { - let spans = as Store>::SlashingSpans::get(controller).unwrap_or_else(|| { - let spans = SlashingSpans::new(window_start); - as Store>::SlashingSpans::insert(controller, &spans); - spans - }); - - InspectingSpans { - dirty: false, - window_start, - controller, - spans, - slash_of: Zero::zero(), - paid_out: Zero::zero(), - reward_proportion, - _marker: sp_std::marker::PhantomData, - } -} - -impl<'a, T: 'a + Config> InspectingSpans<'a, T> { +impl InspectingSpans { fn span_index(&self) -> SpanIndex { self.spans.span_index } fn end_span(&mut self, now: SessionIndex) { - self.dirty = self.spans.end_span(now) || self.dirty; + self.dirty = match self.spans.end_span(now) { + Ok(state) => state, + _ => self.dirty, + }; } /// add some value to the slash of the staker. @@ -486,246 +306,552 @@ impl<'a, T: 'a + Config> InspectingSpans<'a, T> { } } -impl<'a, T: 'a + Config> Drop for InspectingSpans<'a, T> { +impl Drop for InspectingSpans { fn drop(&mut self) { // only update on disk if we slashed this account. if !self.dirty { return; } - if let Some((start, end)) = self.spans.prune(self.window_start) { + if let Ok(Some((start, end))) = self.spans.prune(self.window_start) { for span_index in start..end { as Store>::SpanSlash::remove(&(self.controller.clone(), span_index)); } } - as Store>::SlashingSpans::insert(self.controller, &self.spans); + as Store>::SlashingSpans::insert(&self.controller, self.spans.clone()); } } -/// Clear slashing metadata for an obsolete session. -pub(crate) fn clear_session_metadata(obsolete_session: SessionIndex) { - as Store>::ValidatorSlashInSession::remove_prefix(&obsolete_session, None); - as Store>::NominatorSlashInSession::remove_prefix(&obsolete_session, None); +/// Parameters for performing a slash. +#[derive(Derivative)] +#[derivative(Debug(bound = "T: Debug"), Clone(bound = "T: Clone"))] +pub(crate) struct SlashParams> { + /// The controller account being slashed. + pub(crate) controller: T::AccountId, + /// The proportion of the slash. + pub(crate) slash: Perbill, + /// The exposure of the controller and all nominators. + pub(crate) exposure: ValidatorSnapshot, + /// The session where the offence occurred. + pub(crate) slash_session: SessionIndex, + /// The first era in the current bonding period. + pub(crate) window_start: SessionIndex, + /// The current era. + pub(crate) now: SessionIndex, + /// The maximum percentage of a slash that ever gets paid out. + /// This is f_inf in the paper. + pub(crate) reward_proportion: Perbill, + /// When to disable offenders. + pub(crate) disable_strategy: DisableStrategy, } -/// Clear slashing metadata for a dead account. -pub(crate) fn clear_slash_metadata(controller: &T::AccountId) -> DispatchResult { - let spans = match >::slashing_spans(controller) { - None => return Ok(()), - Some(s) => s, - }; +impl SlashParams +where + T: Config, + MaxNominators: Get, +{ + /// fetches the slashing spans record for a controller account, initializing it if necessary. + fn fetch_spans(&self) -> InspectingSpans { + let spans = as Store>::SlashingSpans::get(&self.controller).unwrap_or_else(|| { + let spans = SlashingSpans::new(self.window_start); + as Store>::SlashingSpans::insert(&self.controller, &spans); + spans + }); - as Store>::SlashingSpans::remove(controller); + InspectingSpans { + dirty: false, + window_start: self.window_start, + controller: self.controller.clone(), + spans: spans, + slash_of: Zero::zero(), + paid_out: Zero::zero(), + reward_proportion: self.reward_proportion, + _marker: sp_std::marker::PhantomData, + } + } - // kill slashing-span metadata for account. - // - // this can only happen while the account is staked _if_ they are completely slashed. - // in that case, they may re-bond, but it would count again as span 0. Further ancient - // slashes would slash into this new bond, since metadata has now been cleared. - for span in spans.iter() { - as Store>::SpanSlash::remove(&(controller.clone(), span.index)); + /// doesn't apply any slash, but kicks out the validator if the misbehavior is from + /// the most recent slashing span. + fn kick_out_if_recent(&self) { + log::trace!("kick_out_if_recent:[{:#?}]", line!()); + + let mut inspect_spans = self.fetch_spans(); + + if inspect_spans.era_span(self.slash_session).map(|s| s.index) == Some(inspect_spans.span_index()) { + inspect_spans.end_span(self.now); + log::trace!( + "kick_out_if_recent:[{:#?}] - Call end_span() | SI[{:#?}] | @[{:#?}]", + line!(), + inspect_spans.span_index(), + self.now, + ); + + if self.disable_strategy == DisableStrategy::Always { + match >::validator_deactivate(&self.controller) { + Err(_) => { + log::error!("kick_out_if_recent:[{:#?}] - validator_deactivate failure", line!()); + } + Ok(_) => (), + } + + // make sure to disable validator till the end of this session + T::SessionInterface::disable_validator(&self.controller); + } + } } - Ok(()) -} + /// Computes a slash of a validator and nominators. It returns an unapplied + /// record to be applied at some later point. Slashing metadata is updated in storage, + /// since unapplied records are only rarely intended to be dropped. + /// + /// The pending slash record returned does not have initialized reporters. Those have + /// to be set at a higher level, if any. + pub(crate) fn compute_slash( + &self, + ) -> Result>, Error> { + log::trace!("compute_slash:[{:#?}] - Slash-[{:#?}]", line!(), self.slash); + + // is the slash amount here a maximum for the era? + let own_slash = self.slash * self.exposure.bond; + if self.slash * self.exposure.total == Zero::zero() { + log::trace!( + "compute_slash:[{:#?}] - ValidatorSS[ B-[{:#?}] | T-[{:#?}]] - Nop", + line!(), + self.exposure.bond, + self.exposure.total, + ); + // kick out the validator even if they won't be slashed, + // as long as the misbehavior is from their most recent slashing span. + self.kick_out_if_recent(); + return Ok(None); + } + + let (prior_slash_p, _era_slash) = + as Store>::ValidatorSlashInSession::get(&self.slash_session, &self.controller) + .unwrap_or((Perbill::zero(), Zero::zero())); + + // compare slash proportions rather than slash values to avoid issues due to rounding + // error. + if self.slash.deconstruct() > prior_slash_p.deconstruct() { + as Store>::ValidatorSlashInSession::insert( + &self.slash_session, + &self.controller, + &(self.slash, own_slash), + ); + } else { + // we slash based on the max in era - this new event is not the max, + // so neither the validator or any nominators will need an update. + // + // this does lead to a divergence of our system from the paper, which + // pays out some reward even if the latest report is not max-in-era. + // we opt to avoid the nominator lookups and edits and leave more rewards + // for more drastic misbehavior. + return Ok(None); + } + + // apply slash to validator. + let mut inspect_spans = self.fetch_spans(); -/// apply the slash to a validator controller account, deducting any missing funds from the reward -/// payout, saturating at 0. this is mildly unfair but also an edge-case that -/// can only occur when overlapping locked funds have been slashed. -fn do_slash_validator( - controller: &T::AccountId, - value: BalanceOf, - reward_payout: &mut BalanceOf, - slashed_imbalance: &mut NegativeImbalanceOf, -) { - as Store>::ValidatorState::mutate(&controller, |validator_state| { - if let Some(validator_state) = validator_state { - let old_active_bond = validator_state.bond; - let valid_pre_total = validator_state.total.saturating_sub(validator_state.nomi_bond_total); - let slashed_value = validator_state.slash(value, T::Currency::minimum_balance()); + let target_span = inspect_spans.compare_and_update_span_slash(self.slash_session, own_slash); + if target_span == Some(inspect_spans.span_index()) { + // misbehavior occurred within the current slashing span - take appropriate + // actions. + + // chill the validator - it misbehaved in the current span and should + // not continue in the next election. also end the slashing span. + inspect_spans.end_span(self.now); log::trace!( - "do_slash_validator:[{:#?}] - [{:#?}] | [{:#?}] | Min [{:#?}]", + "compute_slash:[{:#?}] - Call end_span() | SI[{:#?}] | @[{:#?}]", line!(), - value, - slashed_value, - T::Currency::minimum_balance() + inspect_spans.span_index(), + self.now ); - if !slashed_value.is_zero() { - let pre_balance_stat = T::Currency::free_balance(controller); + if self.disable_strategy != DisableStrategy::Never { + match >::validator_deactivate(&self.controller) { + Err(_) => { + log::error!("kick_out_if_recent:[{:#?}] - validator_deactivate failure", line!()); + } + Ok(_) => (), + } + + // make sure to disable validator till the end of this session + T::SessionInterface::disable_validator(&self.controller); + } + } + + // apply slash to Nominator. + let mut nominators_slashed = Vec::new(); + inspect_spans.paid_out = inspect_spans + .paid_out + .saturating_add(self.slash_nominators(prior_slash_p, &mut nominators_slashed)); + + let mut unapplied_slash: UnappliedSlash = + UnappliedSlash::from_default(self.controller.clone(), inspect_spans.slash_of, inspect_spans.paid_out); - // let (imbalance, missing) = T::Currency::slash_reserved(controller, slashed_value); - let (imbalance, missing) = T::Currency::slash(controller, slashed_value); - slashed_imbalance.subsume(imbalance); + let _ = unapplied_slash.try_update_slashed_nominators(nominators_slashed.as_slice())?; + + Ok(Some(unapplied_slash)) + } - T::Currency::set_lock( - T::StakingLockId::get(), - controller, - valid_pre_total.saturating_sub(slashed_value), - WithdrawReasons::all(), + /// Slash nominators. Accepts general parameters and the prior slash percentage of the + /// validator. + /// + /// Returns the amount of reward to pay out. + fn slash_nominators( + &self, + prior_slash_p: Perbill, + nominators_slashed: &mut Vec<(T::AccountId, BalanceOf)>, + ) -> BalanceOf { + let mut reward_payout: BalanceOf = Zero::zero(); + nominators_slashed.reserve(self.exposure.nominators.len()); + + for nominator in &self.exposure.nominators { + let controller = &nominator.owner; + + // the era slash of a nominator always grows, if the validator + // had a new max slash for the era. + let era_slash = { + let own_slash_prior = prior_slash_p * nominator.amount; + let own_slash_by_validator = self.slash * nominator.amount; + let own_slash_difference = own_slash_by_validator.saturating_sub(own_slash_prior); + + let mut era_slash = + as Store>::NominatorSlashInSession::get(&self.slash_session, self.controller.clone()) + .unwrap_or_else(Zero::zero); + + era_slash = era_slash.saturating_add(own_slash_difference); + + as Store>::NominatorSlashInSession::insert( + &self.slash_session, + self.controller.clone(), + &era_slash, ); - // Consider only the value slashed on active bond. - as Store>::Total::mutate(|x| { - *x = x.saturating_sub(old_active_bond.saturating_sub(validator_state.bond)) - }); + era_slash + }; + + // compare the era slash against other eras in the same span. + let mut spans = self.fetch_spans(); - let cur_balance_stat = T::Currency::free_balance(controller); + let target_span = spans.compare_and_update_span_slash(self.slash_session, era_slash); + if target_span == Some(spans.span_index()) { + // End the span, but don't chill the nominator. its nomination + // on this validator will be ignored in the future. + spans.end_span(self.now); log::trace!( - "do_slash_validator:[{:#?}] - [{:#?}] | [{:#?}]", + "slash_nominators:[{:#?}] - Call end_span() | SI[{:#?}] | @[{:#?}]", line!(), - pre_balance_stat, - cur_balance_stat, + spans.span_index(), + self.now ); + } - if !missing.is_zero() { - // deduct overslash from the reward payout - *reward_payout = reward_payout.saturating_sub(missing); - } + reward_payout = reward_payout.saturating_add(spans.paid_out); + nominators_slashed.push((controller.clone(), spans.slash_of)); + } - if validator_state.is_active() { - >::update_validators_pool(controller.clone(), validator_state.total); - } + reward_payout + } +} - // trigger the event - >::deposit_event(Event::Slash(controller.clone(), slashed_value)); - } - } - }); +/// A pending slash record. The value of the slash has been computed but not applied yet, +/// rather deferred for several eras. +#[derive(Derivative)] +#[derivative(Debug(bound = "T: Debug"), Clone(bound = "T: Clone"))] +#[derive(Decode, Encode, TypeInfo)] +#[scale_info(skip_type_params(T, MaxNominators, MaxReporters))] +pub struct UnappliedSlash, MaxReporters: Get> { + /// The stash ID of the offending validator. + pub(crate) validator: T::AccountId, + /// The validator's own slash. + pub(crate) own: BalanceOf, + /// All other slashed stakers and amounts. + pub(crate) others: BoundedVec<(T::AccountId, BalanceOf), MaxNominators>, + /// Reporters of the offence; bounty payout recipients. + pub(crate) reporters: BoundedVec, + /// The amount of payout. + pub(crate) payout: BalanceOf, } -fn do_slash_nominator( - controller: &T::AccountId, - validator: &T::AccountId, - value: BalanceOf, - reward_payout: &mut BalanceOf, - slashed_imbalance: &mut NegativeImbalanceOf, -) { - as Store>::NominatorState::mutate(&controller, |nominator_state| { - if let Some(nominator_state) = nominator_state { - let old_active_bond = nominator_state.active_bond; +impl MaxEncodedLen for UnappliedSlash +where + T: Config, + MaxNominators: Get, + MaxReporters: Get, +{ + fn max_encoded_len() -> usize { + max(MaxNominators::get(), MaxReporters::get()) as usize + } +} - let slashed_value = - nominator_state.slash_nomination(validator.clone(), value, T::Currency::minimum_balance()); +impl UnappliedSlash +where + T: Config, + MaxNominators: Get, + MaxReporters: Get, +{ + pub(crate) fn from_default(validator: T::AccountId, own_slash: BalanceOf, payout: BalanceOf) -> Self { + let others: BoundedVec<(T::AccountId, BalanceOf), MaxNominators> = bounded_vec![]; + let reporters: BoundedVec = bounded_vec![]; + Self { + validator, + own: own_slash, + others: others, + reporters: reporters, + payout: payout, + } + } - log::trace!( - "do_slash_nominator:[{:#?}] - [{:#?}] | [{:#?}] | Min [{:#?}]", - line!(), - value, - slashed_value, - T::Currency::minimum_balance(), - ); + pub(crate) fn try_update_slashed_nominators( + &mut self, + nominators_slashed: &[(T::AccountId, BalanceOf)], + ) -> Result<(), Error> { + self.others = BoundedVec::try_from(nominators_slashed.to_vec()).map_err(|_| >::NominationOverflow)?; + Ok(()) + } - if !slashed_value.is_zero() { - let pre_balance_stat = T::Currency::free_balance(controller); + /// Apply a previously-unapplied slash. + pub(crate) fn apply_slash(&mut self) { + let mut slashed_imbalance = NegativeImbalanceOf::::zero(); - as Store>::ValidatorState::mutate(&validator, |validator_state| { - if let Some(validator_state) = validator_state { - validator_state.dec_nominator(controller.clone(), slashed_value); - if validator_state.is_active() { - >::update_validators_pool(validator.clone(), validator_state.total); - } - } - }); + self.do_slash_validator(&mut slashed_imbalance); - // let (imbalance, missing) = T::Currency::slash_reserved(controller, slashed_value); - let (imbalance, missing) = T::Currency::slash(controller, slashed_value); - slashed_imbalance.subsume(imbalance); + for &(ref nominator, nominator_slash) in &self.others.to_vec() { + self.do_slash_nominator(nominator, nominator_slash, &mut slashed_imbalance); + } - T::Currency::set_lock( - T::StakingLockId::get(), - controller, - nominator_state.total, - WithdrawReasons::all(), + match >::validator_stake_reconciliation(&self.validator) { + Err(_) => { + log::error!( + "apply_slash:[{:#?}] - Reconciliation failure for Validator[{:#?}]", + line!(), + &self.validator ); + } + Ok(_) => (), + } - // Consider only the value slashed on active bond. - as Store>::Total::mutate(|x| { - *x = x.saturating_sub(old_active_bond.saturating_sub(nominator_state.active_bond)) - }); + self.pay_reporters(slashed_imbalance); + } - let cur_balance_stat = T::Currency::free_balance(controller); + /// apply the slash to a validator controller account, deducting any missing funds from the + /// reward payout, saturating at 0. this is mildly unfair but also an edge-case that + /// can only occur when overlapping locked funds have been slashed. + fn do_slash_validator(&mut self, slashed_imbalance: &mut NegativeImbalanceOf) { + as Store>::ValidatorState::mutate(&self.validator, |validator_state| { + if let Some(validator_state) = validator_state { + let old_active_bond = validator_state.bond; + let valid_pre_total = validator_state.total.saturating_sub(validator_state.nomi_bond_total); + let slashed_value = validator_state.slash(self.own, T::Currency::minimum_balance()); log::trace!( - "do_slash_nominator:[{:#?}] - [{:#?}] | [{:#?}]", + "do_slash_validator:[{:#?}] - [{:#?}] | [{:#?}] | Min [{:#?}]", line!(), - pre_balance_stat, - cur_balance_stat, + self.own, + slashed_value, + T::Currency::minimum_balance() ); - if !missing.is_zero() { - // deduct overslash from the reward payout - *reward_payout = reward_payout.saturating_sub(missing); + if !slashed_value.is_zero() { + let pre_balance_stat = T::Currency::free_balance(&self.validator); + + let (imbalance, missing) = T::Currency::slash(&self.validator, slashed_value); + slashed_imbalance.subsume(imbalance); + + T::Currency::set_lock( + T::StakingLockId::get(), + &self.validator, + valid_pre_total.saturating_sub(slashed_value), + WithdrawReasons::all(), + ); + + // Consider only the value slashed on active bond. + as Store>::Total::mutate(|x| { + *x = x.saturating_sub(old_active_bond.saturating_sub(validator_state.bond)) + }); + + let cur_balance_stat = T::Currency::free_balance(&self.validator); + + log::trace!( + "do_slash_validator:[{:#?}] - [{:#?}] | [{:#?}]", + line!(), + pre_balance_stat, + cur_balance_stat, + ); + + if !missing.is_zero() { + // deduct overslash from the reward payout + self.payout = self.payout.saturating_sub(missing); + } + + if validator_state.is_active() { + match >::update_validators_pool(&self.validator, validator_state.total) { + Err(_) => { + log::error!( + "do_slash_validator:[{:#?}] - update_validators_pool Overflow Error", + line!() + ); + } + Ok(_) => (), + } + } + + // trigger the event + >::deposit_event(Event::Slash(self.validator.clone(), slashed_value)); } + } + }); + } + + fn do_slash_nominator( + &mut self, + controller: &T::AccountId, + value: BalanceOf, + slashed_imbalance: &mut NegativeImbalanceOf, + ) { + as Store>::NominatorState::mutate(&controller, |nominator_state| { + if let Some(nominator_state) = nominator_state { + let old_active_bond = nominator_state.active_bond; + + let slashed_value = + nominator_state.slash_nomination(&self.validator, value, T::Currency::minimum_balance()); + + log::trace!( + "do_slash_nominator:[{:#?}] - [{:#?}] | [{:#?}] | Min [{:#?}]", + line!(), + value, + slashed_value, + T::Currency::minimum_balance(), + ); - // trigger the event - >::deposit_event(Event::Slash(controller.clone(), slashed_value)); + if !slashed_value.is_zero() { + let pre_balance_stat = T::Currency::free_balance(controller); + + as Store>::ValidatorState::mutate(&self.validator, |validator_state| { + if let Some(validator_state) = validator_state { + match validator_state.dec_nominator(controller, slashed_value) { + Err(err) => { + log::error!( + "do_slash_nominator:[{:#?}] - dec_nominator failure[{:#?}]", + line!(), + err, + ); + } + Ok(_) => (), + } + + if validator_state.is_active() { + match >::update_validators_pool(&self.validator, validator_state.total) { + Err(_) => { + log::error!( + "do_slash_nominator:[{:#?}] - update_validators_pool Overflow Error", + line!() + ); + } + Ok(_) => (), + } + } + } + }); + + // let (imbalance, missing) = T::Currency::slash_reserved(controller, slashed_value); + let (imbalance, missing) = T::Currency::slash(controller, slashed_value); + slashed_imbalance.subsume(imbalance); + + T::Currency::set_lock( + T::StakingLockId::get(), + controller, + nominator_state.total, + WithdrawReasons::all(), + ); + + // Consider only the value slashed on active bond. + as Store>::Total::mutate(|x| { + *x = x.saturating_sub(old_active_bond.saturating_sub(nominator_state.active_bond)) + }); + + let cur_balance_stat = T::Currency::free_balance(controller); + + log::trace!( + "do_slash_nominator:[{:#?}] - [{:#?}] | [{:#?}]", + line!(), + pre_balance_stat, + cur_balance_stat, + ); + + if !missing.is_zero() { + // deduct overslash from the reward payout + self.payout = self.payout.saturating_sub(missing); + } + + // trigger the event + >::deposit_event(Event::Slash(controller.clone(), slashed_value)); + } } + }); + } + + /// Apply a reward payout to some reporters, paying the rewards out of the slashed imbalance. + fn pay_reporters(&mut self, slashed_imbalance: NegativeImbalanceOf) { + let reporters: Vec = self.reporters.clone().to_vec(); + + if self.payout.is_zero() || reporters.is_empty() { + // nobody to pay out to or nothing to pay; + // just treat the whole value as slashed. + T::Slash::on_unbalanced(slashed_imbalance); + return; } - }); -} -/// Apply a previously-unapplied slash. -pub(crate) fn apply_slash(unapplied_slash: UnappliedSlash>) { - let mut slashed_imbalance = NegativeImbalanceOf::::zero(); - let mut reward_payout = unapplied_slash.payout; - - do_slash_validator::( - &unapplied_slash.validator, - unapplied_slash.own, - &mut reward_payout, - &mut slashed_imbalance, - ); - - for &(ref nominator, nominator_slash) in &unapplied_slash.others { - do_slash_nominator::( - nominator, - &unapplied_slash.validator, - nominator_slash, - &mut reward_payout, - &mut slashed_imbalance, - ); - } + // take rewards out of the slashed imbalance. + let reward_payout = self.payout.min(slashed_imbalance.peek()); + let (mut reward_payout, mut value_slashed) = slashed_imbalance.split(reward_payout); - >::validator_stake_reconciliation(&unapplied_slash.validator); + let per_reporter = reward_payout.peek() / (reporters.len() as u32).into(); + for reporter in reporters { + let (reporter_reward, rest) = reward_payout.split(per_reporter); + reward_payout = rest; + let reporter_reward_peek = reporter_reward.peek(); - pay_reporters::(reward_payout, slashed_imbalance, &unapplied_slash.reporters); -} + // this cancels out the reporter reward imbalance internally, leading + // to no change in total issuance. + T::Currency::resolve_creating(&reporter, reporter_reward); + + >::deposit_event(Event::PayReporterReward(reporter.clone(), reporter_reward_peek)); + } -/// Apply a reward payout to some reporters, paying the rewards out of the slashed imbalance. -fn pay_reporters( - reward_payout: BalanceOf, - slashed_imbalance: NegativeImbalanceOf, - reporters: &[T::AccountId], -) { - if reward_payout.is_zero() || reporters.is_empty() { - // nobody to pay out to or nothing to pay; - // just treat the whole value as slashed. - T::Slash::on_unbalanced(slashed_imbalance); - return; + // the rest goes to the on-slash imbalance handler (e.g. treasury) + value_slashed.subsume(reward_payout); // remainder of reward division remains. + T::Slash::on_unbalanced(value_slashed); } +} - // take rewards out of the slashed imbalance. - let reward_payout = reward_payout.min(slashed_imbalance.peek()); - let (mut reward_payout, mut value_slashed) = slashed_imbalance.split(reward_payout); +/// Clear slashing metadata for an obsolete session. +pub(crate) fn clear_session_metadata(obsolete_session: SessionIndex) { + as Store>::ValidatorSlashInSession::remove_prefix(&obsolete_session, None); + as Store>::NominatorSlashInSession::remove_prefix(&obsolete_session, None); +} - let per_reporter = reward_payout.peek() / (reporters.len() as u32).into(); - for reporter in reporters { - let (reporter_reward, rest) = reward_payout.split(per_reporter); - reward_payout = rest; - let reporter_reward_peek = reporter_reward.peek(); +/// Clear slashing metadata for a dead account. +pub(crate) fn clear_slash_metadata(controller: &T::AccountId) -> DispatchResult { + let spans = match >::slashing_spans(controller) { + None => return Ok(()), + Some(s) => s, + }; - // this cancels out the reporter reward imbalance internally, leading - // to no change in total issuance. - T::Currency::resolve_creating(reporter, reporter_reward); + as Store>::SlashingSpans::remove(controller); - >::deposit_event(Event::PayReporterReward(reporter.clone(), reporter_reward_peek)); + // kill slashing-span metadata for account. + // + // this can only happen while the account is staked _if_ they are completely slashed. + // in that case, they may re-bond, but it would count again as span 0. Further ancient + // slashes would slash into this new bond, since metadata has now been cleared. + for span in spans.iter() { + as Store>::SpanSlash::remove(&(controller.clone(), span.index)); } - // the rest goes to the on-slash imbalance handler (e.g. treasury) - value_slashed.subsume(reward_payout); // remainder of reward division remains. - T::Slash::on_unbalanced(value_slashed); + Ok(()) } diff --git a/pallets/staking/src/tests.rs b/pallets/staking/src/tests.rs index 1c25bf485cc..2d90848df0d 100644 --- a/pallets/staking/src/tests.rs +++ b/pallets/staking/src/tests.rs @@ -19,10 +19,9 @@ use super::*; use crate::mock::{ - balances, bond_nominator, bond_validator, events, is_disabled, last_event, on_offence_in_session, on_offence_now, - set_author, start_session, Balance, Balances, CancelOrigin, Event as MetaEvent, ExtBuilder, NodleStaking, Origin, - Session, System, Test, -}; + balances, bond_nominator, bond_validator, events, is_disabled, last_event, set_author, start_session, Balance, + Balances, CancelOrigin, Event as MetaEvent, ExtBuilder, NodleStaking, Origin, Session, System, Test, +}; //on_offence_in_session, on_offence_now, use crate::set::OrderedSet; use crate::types::{Bond, StakeReward, ValidatorSnapshot, ValidatorStatus}; use frame_support::{assert_noop, assert_ok, traits::Currency}; @@ -149,5278 +148,3 @@ fn validator_activate_works() { // panic!("S-1"); }); } - -#[test] -fn disablestrategy_whenslashed_works() { - ExtBuilder::default().num_validators(4).build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(events(), expected); - - assert!(Session::validators().contains(&11)); - assert!(>::contains_key(11)); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(Balances::total_balance(&11), 2000); - - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(Balances::total_balance(&101), 2000); - - assert_eq!(NodleStaking::total(), 3500); - - let slash_percent = Perbill::from_percent(25); - let initial_exposure = NodleStaking::at_stake(NodleStaking::active_session(), 11); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - mock::on_offence_now( - &[OffenceDetails { - offender: (11, initial_exposure), - reporters: vec![], - }], - &[slash_percent], - DisableStrategy::WhenSlashed, - ); - - assert_eq!(NodleStaking::validator_state(11).unwrap().state, ValidatorStatus::Idle); - - assert!(is_disabled(11)); - - mock::start_active_session(1); - - assert_eq!(NodleStaking::validator_state(11).unwrap().state, ValidatorStatus::Idle); - - assert!(is_disabled(11)); - - let mut new1 = vec![Event::Slash(11, 250), Event::Slash(101, 125)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_noop!( - NodleStaking::validator_bond_more(Origin::signed(11), 2000), - Error::::InsufficientBalance, - ); - - assert_ok!(NodleStaking::validator_bond_more(Origin::signed(11), 10)); - - let mut new1 = vec![Event::ValidatorBondedMore(11, 750, 760)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&11), (1750, 760)); - assert_eq!(Balances::total_balance(&11), 1750); - - assert_eq!(mock::balances(&101), (1875, 375)); - assert_eq!(Balances::total_balance(&101), 1875); - - assert_eq!(NodleStaking::total(), 3135); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - // panic!("S-1"); - }); -} - -#[test] -fn kick_out_if_recent_disablestrategy_whenslashed_works() { - ExtBuilder::default().num_validators(4).build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(events(), expected); - - assert!(Session::validators().contains(&11)); - assert!(>::contains_key(11)); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(Balances::total_balance(&11), 2000); - - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(Balances::total_balance(&101), 2000); - - assert_eq!(NodleStaking::total(), 3500); - - let slash_percent = Perbill::from_percent(0); - let initial_exposure = NodleStaking::at_stake(NodleStaking::active_session(), 11); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - mock::on_offence_now( - &[OffenceDetails { - offender: (11, initial_exposure), - reporters: vec![], - }], - &[slash_percent], - DisableStrategy::WhenSlashed, - ); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - assert!(!is_disabled(11)); - - mock::start_active_session(1); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - assert!(!is_disabled(11)); - - assert_noop!( - NodleStaking::validator_bond_more(Origin::signed(11), 2000), - Error::::InsufficientBalance, - ); - - assert_ok!(NodleStaking::validator_bond_more(Origin::signed(11), 10)); - - let mut new1 = vec![Event::ValidatorBondedMore(11, 1000, 1010)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&11), (2000, 1010)); - assert_eq!(Balances::total_balance(&11), 2000); - - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(Balances::total_balance(&101), 2000); - - assert_eq!(NodleStaking::total(), 3510); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - // panic!("S-1"); - }); -} - -#[test] -fn disablestrategy_never_works() { - ExtBuilder::default().num_validators(4).build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(events(), expected); - - assert!(Session::validators().contains(&11)); - assert!(>::contains_key(11)); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(Balances::total_balance(&11), 2000); - - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(Balances::total_balance(&101), 2000); - - assert_eq!(NodleStaking::total(), 3500); - - let slash_percent = Perbill::from_percent(25); - let initial_exposure = NodleStaking::at_stake(NodleStaking::active_session(), 11); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - mock::on_offence_now( - &[OffenceDetails { - offender: (11, initial_exposure), - reporters: vec![], - }], - &[slash_percent], - DisableStrategy::Never, - ); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - assert!(!is_disabled(11)); - - mock::start_active_session(1); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - assert!(!is_disabled(11)); - - let mut new1 = vec![Event::Slash(11, 250), Event::Slash(101, 125)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_noop!( - NodleStaking::validator_bond_more(Origin::signed(11), 2000), - Error::::InsufficientBalance, - ); - - assert_ok!(NodleStaking::validator_bond_more(Origin::signed(11), 10)); - - let mut new1 = vec![Event::ValidatorBondedMore(11, 750, 760)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&11), (1750, 760)); - assert_eq!(Balances::total_balance(&11), 1750); - - assert_eq!(mock::balances(&101), (1875, 375)); - assert_eq!(Balances::total_balance(&101), 1875); - - assert_eq!(NodleStaking::total(), 3135); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - // panic!("S-1"); - }); -} - -#[test] -fn kick_out_if_recent_disablestrategy_never_works() { - ExtBuilder::default().num_validators(4).build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(events(), expected); - - assert!(Session::validators().contains(&11)); - assert!(>::contains_key(11)); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(Balances::total_balance(&11), 2000); - - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(Balances::total_balance(&101), 2000); - - assert_eq!(NodleStaking::total(), 3500); - - let slash_percent = Perbill::from_percent(0); - let initial_exposure = NodleStaking::at_stake(NodleStaking::active_session(), 11); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - mock::on_offence_now( - &[OffenceDetails { - offender: (11, initial_exposure), - reporters: vec![], - }], - &[slash_percent], - DisableStrategy::Never, - ); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - assert!(!is_disabled(11)); - - mock::start_active_session(1); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - assert!(!is_disabled(11)); - - assert_noop!( - NodleStaking::validator_bond_more(Origin::signed(11), 2000), - Error::::InsufficientBalance, - ); - - assert_ok!(NodleStaking::validator_bond_more(Origin::signed(11), 10)); - - let mut new1 = vec![Event::ValidatorBondedMore(11, 1000, 1010)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&11), (2000, 1010)); - assert_eq!(Balances::total_balance(&11), 2000); - - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(Balances::total_balance(&101), 2000); - - assert_eq!(NodleStaking::total(), 3510); - - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - // panic!("S-1"); - }); -} - -#[test] -fn validator_exit_executes_after_delay() { - ExtBuilder::default() - .with_balances(vec![ - (1, 1000), - (2, 300), - (3, 100), - (4, 100), - (5, 100), - (6, 100), - (7, 100), - (8, 9), - (9, 4), - ]) - .with_validators(vec![(1, 500), (2, 200)]) - .with_nominators(vec![(3, 1, 100), (4, 1, 100), (5, 2, 100), (6, 2, 100)]) - .tst_staking_build() - .execute_with(|| { - mock::start_active_session(4); - - let mut expected = vec![ - Event::ValidatorChosen(2, 1, 700), - Event::ValidatorChosen(2, 2, 400), - Event::NewSession(5, 2, 2, 1100), - Event::ValidatorChosen(3, 1, 700), - Event::ValidatorChosen(3, 2, 400), - Event::NewSession(10, 3, 2, 1100), - Event::ValidatorChosen(4, 1, 700), - Event::ValidatorChosen(4, 2, 400), - Event::NewSession(15, 4, 2, 1100), - Event::ValidatorChosen(5, 1, 700), - Event::ValidatorChosen(5, 2, 400), - Event::NewSession(20, 5, 2, 1100), - ]; - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&1), (1000, 500)); - assert_eq!(Balances::total_balance(&1), 1000); - - assert_eq!(mock::balances(&3), (100, 100)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 100)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&2), (300, 200)); - assert_eq!(Balances::total_balance(&2), 300); - - assert_eq!(mock::balances(&5), (100, 100)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 100)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(NodleStaking::total(), 1100); - - assert_noop!( - NodleStaking::validator_exit_pool(Origin::signed(3)), - Error::::ValidatorDNE, - ); - mock::start_active_session(6); - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(2))); - assert_eq!( - last_event(), - MetaEvent::NodleStaking(Event::ValidatorScheduledExit(6, 2, 8)), - ); - - let mut new1 = vec![ - Event::ValidatorChosen(6, 1, 700), - Event::ValidatorChosen(6, 2, 400), - Event::NewSession(25, 6, 2, 1100), - Event::ValidatorChosen(7, 1, 700), - Event::ValidatorChosen(7, 2, 400), - Event::NewSession(30, 7, 2, 1100), - Event::ValidatorScheduledExit(6, 2, 8), - ]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - let info = NodleStaking::validator_state(&2).unwrap(); - assert_eq!(info.state, ValidatorStatus::Leaving(8)); - mock::start_active_session(9); - // we must exclude leaving collators from rewards while - // holding them retroactively accountable for previous faults - // (within the last T::SlashingWindow blocks) - let mut new2 = vec![ - Event::ValidatorChosen(8, 1, 700), - Event::NewSession(35, 8, 1, 700), - Event::NominatorLeftValidator(5, 2, 100, 0), - Event::NominatorLeftValidator(6, 2, 100, 0), - Event::ValidatorLeft(2, 400, 700), - Event::ValidatorChosen(9, 1, 700), - Event::NewSession(40, 9, 1, 700), - Event::ValidatorChosen(10, 1, 700), - Event::NewSession(45, 10, 1, 700), - ]; - expected.append(&mut new2); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&1), (1000, 500)); - assert_eq!(Balances::total_balance(&1), 1000); - - assert_eq!(mock::balances(&3), (100, 100)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 100)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&2), (300, 0)); - assert_eq!(Balances::total_balance(&2), 300); - - assert_eq!(mock::balances(&5), (100, 100)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 100)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(NodleStaking::total(), 700); - - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(5))); - - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(6))); - - let mut new3 = vec![ - Event::Withdrawn(5, 100), - Event::NominatorLeft(5, 100), - Event::Withdrawn(6, 100), - Event::NominatorLeft(6, 100), - ]; - expected.append(&mut new3); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&5), (100, 0)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 0)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(NodleStaking::total(), 700); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - // panic!("S-1"); - }); -} - -#[test] -fn validator_selection_chooses_top_candidates() { - ExtBuilder::default() - .with_balances(vec![ - (1, 1000), - (2, 1000), - (3, 1000), - (4, 1000), - (5, 1000), - (6, 1000), - (7, 33), - (8, 33), - (9, 33), - ]) - .with_validators(vec![(1, 100), (2, 90), (3, 80), (4, 70), (5, 60), (6, 50)]) - .tst_staking_build() - .execute_with(|| { - mock::start_active_session(4); - - let mut expected = vec![ - Event::ValidatorChosen(2, 1, 100), - Event::ValidatorChosen(2, 2, 90), - Event::ValidatorChosen(2, 3, 80), - Event::ValidatorChosen(2, 4, 70), - Event::ValidatorChosen(2, 5, 60), - Event::NewSession(5, 2, 5, 400), - Event::ValidatorChosen(3, 1, 100), - Event::ValidatorChosen(3, 2, 90), - Event::ValidatorChosen(3, 3, 80), - Event::ValidatorChosen(3, 4, 70), - Event::ValidatorChosen(3, 5, 60), - Event::NewSession(10, 3, 5, 400), - Event::ValidatorChosen(4, 1, 100), - Event::ValidatorChosen(4, 2, 90), - Event::ValidatorChosen(4, 3, 80), - Event::ValidatorChosen(4, 4, 70), - Event::ValidatorChosen(4, 5, 60), - Event::NewSession(15, 4, 5, 400), - Event::ValidatorChosen(5, 1, 100), - Event::ValidatorChosen(5, 2, 90), - Event::ValidatorChosen(5, 3, 80), - Event::ValidatorChosen(5, 4, 70), - Event::ValidatorChosen(5, 5, 60), - Event::NewSession(20, 5, 5, 400), - ]; - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&1), (1000, 100)); - assert_eq!(Balances::total_balance(&1), 1000); - - assert_eq!(mock::balances(&2), (1000, 90)); - assert_eq!(Balances::total_balance(&2), 1000); - - assert_eq!(mock::balances(&3), (1000, 80)); - assert_eq!(Balances::total_balance(&3), 1000); - - assert_eq!(mock::balances(&4), (1000, 70)); - assert_eq!(Balances::total_balance(&4), 1000); - - assert_eq!(mock::balances(&5), (1000, 60)); - assert_eq!(Balances::total_balance(&5), 1000); - - assert_eq!(mock::balances(&6), (1000, 50)); - assert_eq!(Balances::total_balance(&6), 1000); - - assert_eq!(NodleStaking::total(), 450); - - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(6))); - assert_eq!( - last_event(), - MetaEvent::NodleStaking(Event::ValidatorScheduledExit(4, 6, 6)), - ); - let info = NodleStaking::validator_state(&6).unwrap(); - assert_eq!(info.state, ValidatorStatus::Leaving(6)); - - mock::start_active_session(6); - - let mut new1 = vec![ - Event::ValidatorScheduledExit(4, 6, 6), - Event::ValidatorChosen(6, 1, 100), - Event::ValidatorChosen(6, 2, 90), - Event::ValidatorChosen(6, 3, 80), - Event::ValidatorChosen(6, 4, 70), - Event::ValidatorChosen(6, 5, 60), - Event::NewSession(25, 6, 5, 400), - Event::ValidatorLeft(6, 50, 400), - Event::ValidatorChosen(7, 1, 100), - Event::ValidatorChosen(7, 2, 90), - Event::ValidatorChosen(7, 3, 80), - Event::ValidatorChosen(7, 4, 70), - Event::ValidatorChosen(7, 5, 60), - Event::NewSession(30, 7, 5, 400), - ]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&6), (1000, 0)); - assert_eq!(Balances::total_balance(&6), 1000); - - assert_eq!(NodleStaking::total(), 400); - - assert_ok!(NodleStaking::validator_join_pool(Origin::signed(6), 69u128)); - - assert_eq!( - mock::last_event(), - MetaEvent::NodleStaking(Event::JoinedValidatorPool(6, 69, 469u128)) - ); - - mock::start_active_session(8); - - let mut new2 = vec![ - Event::JoinedValidatorPool(6, 69, 469), - Event::ValidatorChosen(8, 1, 100), - Event::ValidatorChosen(8, 2, 90), - Event::ValidatorChosen(8, 3, 80), - Event::ValidatorChosen(8, 4, 70), - Event::ValidatorChosen(8, 6, 69), - Event::NewSession(35, 8, 5, 409), - Event::ValidatorChosen(9, 1, 100), - Event::ValidatorChosen(9, 2, 90), - Event::ValidatorChosen(9, 3, 80), - Event::ValidatorChosen(9, 4, 70), - Event::ValidatorChosen(9, 6, 69), - Event::NewSession(40, 9, 5, 409), - ]; - expected.append(&mut new2); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&6), (1000, 69)); - assert_eq!(Balances::total_balance(&6), 1000); - - assert_eq!(NodleStaking::total(), 469); - - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(1))); - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(2))); - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(3))); - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(4))); - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(5))); - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(6))); - - let mut new3 = vec![ - Event::ValidatorScheduledExit(8, 1, 10), - Event::ValidatorScheduledExit(8, 2, 10), - Event::ValidatorScheduledExit(8, 3, 10), - Event::ValidatorScheduledExit(8, 4, 10), - Event::ValidatorScheduledExit(8, 5, 10), - Event::ValidatorScheduledExit(8, 6, 10), - ]; - expected.append(&mut new3); - assert_eq!(events(), expected); - - mock::start_active_session(10); - - let mut new4 = vec![ - Event::NewSession(45, 10, 0, 0), - Event::ValidatorLeft(1, 100, 369), - Event::ValidatorLeft(2, 90, 279), - Event::ValidatorLeft(3, 80, 199), - Event::ValidatorLeft(4, 70, 129), - Event::ValidatorLeft(5, 60, 69), - Event::ValidatorLeft(6, 69, 0), - Event::NewSession(50, 11, 0, 0), - ]; - expected.append(&mut new4); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&1), (1000, 0)); - assert_eq!(Balances::total_balance(&1), 1000); - - assert_eq!(mock::balances(&2), (1000, 0)); - assert_eq!(Balances::total_balance(&2), 1000); - - assert_eq!(mock::balances(&3), (1000, 0)); - assert_eq!(Balances::total_balance(&3), 1000); - - assert_eq!(mock::balances(&4), (1000, 0)); - assert_eq!(Balances::total_balance(&4), 1000); - - assert_eq!(mock::balances(&5), (1000, 0)); - assert_eq!(Balances::total_balance(&5), 1000); - - assert_eq!(mock::balances(&6), (1000, 0)); - assert_eq!(Balances::total_balance(&6), 1000); - - assert_eq!(NodleStaking::total(), 0); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - // panic!("S-1"); - }); -} - -#[test] -fn exit_queue() { - ExtBuilder::default() - .with_balances(vec![ - (1, 1000), - (2, 1000), - (3, 1000), - (4, 1000), - (5, 1000), - (6, 1000), - (7, 33), - (8, 33), - (9, 33), - ]) - .with_validators(vec![(1, 100), (2, 90), (3, 80), (4, 70), (5, 60), (6, 50)]) - .tst_staking_build() - .execute_with(|| { - mock::start_active_session(4); - - let mut expected = vec![ - Event::ValidatorChosen(2, 1, 100), - Event::ValidatorChosen(2, 2, 90), - Event::ValidatorChosen(2, 3, 80), - Event::ValidatorChosen(2, 4, 70), - Event::ValidatorChosen(2, 5, 60), - Event::NewSession(5, 2, 5, 400), - Event::ValidatorChosen(3, 1, 100), - Event::ValidatorChosen(3, 2, 90), - Event::ValidatorChosen(3, 3, 80), - Event::ValidatorChosen(3, 4, 70), - Event::ValidatorChosen(3, 5, 60), - Event::NewSession(10, 3, 5, 400), - Event::ValidatorChosen(4, 1, 100), - Event::ValidatorChosen(4, 2, 90), - Event::ValidatorChosen(4, 3, 80), - Event::ValidatorChosen(4, 4, 70), - Event::ValidatorChosen(4, 5, 60), - Event::NewSession(15, 4, 5, 400), - Event::ValidatorChosen(5, 1, 100), - Event::ValidatorChosen(5, 2, 90), - Event::ValidatorChosen(5, 3, 80), - Event::ValidatorChosen(5, 4, 70), - Event::ValidatorChosen(5, 5, 60), - Event::NewSession(20, 5, 5, 400), - ]; - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&1), (1000, 100)); - assert_eq!(Balances::total_balance(&1), 1000); - - assert_eq!(mock::balances(&2), (1000, 90)); - assert_eq!(Balances::total_balance(&2), 1000); - - assert_eq!(mock::balances(&3), (1000, 80)); - assert_eq!(Balances::total_balance(&3), 1000); - - assert_eq!(mock::balances(&4), (1000, 70)); - assert_eq!(Balances::total_balance(&4), 1000); - - assert_eq!(mock::balances(&5), (1000, 60)); - assert_eq!(Balances::total_balance(&5), 1000); - - assert_eq!(mock::balances(&6), (1000, 50)); - assert_eq!(Balances::total_balance(&6), 1000); - - assert_eq!(NodleStaking::total(), 450); - - mock::start_active_session(5); - - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(6))); - - let mut new1 = vec![ - Event::ValidatorChosen(6, 1, 100), - Event::ValidatorChosen(6, 2, 90), - Event::ValidatorChosen(6, 3, 80), - Event::ValidatorChosen(6, 4, 70), - Event::ValidatorChosen(6, 5, 60), - Event::NewSession(25, 6, 5, 400), - Event::ValidatorScheduledExit(5, 6, 7), - ]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - mock::start_active_session(6); - - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(5))); - - let mut new2 = vec![ - Event::ValidatorChosen(7, 1, 100), - Event::ValidatorChosen(7, 2, 90), - Event::ValidatorChosen(7, 3, 80), - Event::ValidatorChosen(7, 4, 70), - Event::ValidatorChosen(7, 5, 60), - Event::NewSession(30, 7, 5, 400), - Event::ValidatorScheduledExit(6, 5, 8), - ]; - - expected.append(&mut new2); - assert_eq!(events(), expected); - - mock::start_active_session(7); - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(4))); - - let mut new3 = vec![ - Event::ValidatorLeft(6, 50, 400), - Event::ValidatorChosen(8, 1, 100), - Event::ValidatorChosen(8, 2, 90), - Event::ValidatorChosen(8, 3, 80), - Event::ValidatorChosen(8, 4, 70), - Event::NewSession(35, 8, 4, 340), - Event::ValidatorScheduledExit(7, 4, 9), - ]; - - expected.append(&mut new3); - assert_eq!(events(), expected); - - mock::start_active_session(8); - - assert_noop!( - NodleStaking::validator_exit_pool(Origin::signed(4)), - Error::::AlreadyLeaving, - ); - - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(3))); - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(2))); - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(1))); - - let mut new4 = vec![ - Event::ValidatorLeft(5, 60, 340), - Event::ValidatorChosen(9, 1, 100), - Event::ValidatorChosen(9, 2, 90), - Event::ValidatorChosen(9, 3, 80), - Event::NewSession(40, 9, 3, 270), - Event::ValidatorScheduledExit(8, 3, 10), - Event::ValidatorScheduledExit(8, 2, 10), - Event::ValidatorScheduledExit(8, 1, 10), - ]; - expected.append(&mut new4); - assert_eq!(events(), expected); - - mock::start_active_session(10); - - let mut new5 = vec![ - Event::ValidatorLeft(4, 70, 270), - Event::NewSession(45, 10, 0, 0), - Event::ValidatorLeft(1, 100, 170), - Event::ValidatorLeft(2, 90, 80), - Event::ValidatorLeft(3, 80, 0), - Event::NewSession(50, 11, 0, 0), - ]; - expected.append(&mut new5); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&1), (1000, 0)); - assert_eq!(Balances::total_balance(&1), 1000); - - assert_eq!(mock::balances(&2), (1000, 0)); - assert_eq!(Balances::total_balance(&2), 1000); - - assert_eq!(mock::balances(&3), (1000, 0)); - assert_eq!(Balances::total_balance(&3), 1000); - - assert_eq!(mock::balances(&4), (1000, 0)); - assert_eq!(Balances::total_balance(&4), 1000); - - assert_eq!(mock::balances(&5), (1000, 0)); - assert_eq!(Balances::total_balance(&5), 1000); - - assert_eq!(mock::balances(&6), (1000, 0)); - assert_eq!(Balances::total_balance(&6), 1000); - - assert_eq!(NodleStaking::total(), 0); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - // panic!("S-1"); - }); -} - -#[test] -fn payout_distribution_to_solo_validators() { - ExtBuilder::default() - .existential_deposit(3) - .with_balances(vec![ - (1, 1000), - (2, 1000), - (3, 1000), - (4, 1000), - (5, 1000), - (6, 1000), - (7, 33), - (8, 33), - (9, 33), - ]) - .with_validators(vec![(1, 100), (2, 90), (3, 80), (4, 70), (5, 60), (6, 50)]) - .tst_staking_build() - .execute_with(|| { - mock::start_active_session(4); - - let mut expected = vec![ - Event::ValidatorChosen(2, 1, 100), - Event::ValidatorChosen(2, 2, 90), - Event::ValidatorChosen(2, 3, 80), - Event::ValidatorChosen(2, 4, 70), - Event::ValidatorChosen(2, 5, 60), - Event::NewSession(5, 2, 5, 400), - Event::ValidatorChosen(3, 1, 100), - Event::ValidatorChosen(3, 2, 90), - Event::ValidatorChosen(3, 3, 80), - Event::ValidatorChosen(3, 4, 70), - Event::ValidatorChosen(3, 5, 60), - Event::NewSession(10, 3, 5, 400), - Event::ValidatorChosen(4, 1, 100), - Event::ValidatorChosen(4, 2, 90), - Event::ValidatorChosen(4, 3, 80), - Event::ValidatorChosen(4, 4, 70), - Event::ValidatorChosen(4, 5, 60), - Event::NewSession(15, 4, 5, 400), - Event::ValidatorChosen(5, 1, 100), - Event::ValidatorChosen(5, 2, 90), - Event::ValidatorChosen(5, 3, 80), - Event::ValidatorChosen(5, 4, 70), - Event::ValidatorChosen(5, 5, 60), - Event::NewSession(20, 5, 5, 400), - ]; - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&1), (1000, 100)); - assert_eq!(Balances::total_balance(&1), 1000); - - assert_eq!(mock::balances(&2), (1000, 90)); - assert_eq!(Balances::total_balance(&2), 1000); - - assert_eq!(mock::balances(&3), (1000, 80)); - assert_eq!(Balances::total_balance(&3), 1000); - - assert_eq!(mock::balances(&4), (1000, 70)); - assert_eq!(Balances::total_balance(&4), 1000); - - assert_eq!(mock::balances(&5), (1000, 60)); - assert_eq!(Balances::total_balance(&5), 1000); - - assert_eq!(mock::balances(&6), (1000, 50)); - assert_eq!(Balances::total_balance(&6), 1000); - - assert_eq!(NodleStaking::total(), 450); - - // Session-5 - set block author as 1 for all blocks - mock::start_active_session(5); - - mock::set_author(5, 1, 100); - mock::mint_rewards(1_000_000); - - mock::start_active_session(6); - - let mut new1 = vec![ - Event::ValidatorChosen(6, 1, 100), - Event::ValidatorChosen(6, 2, 90), - Event::ValidatorChosen(6, 3, 80), - Event::ValidatorChosen(6, 4, 70), - Event::ValidatorChosen(6, 5, 60), - Event::NewSession(25, 6, 5, 400), - Event::StakeReward(1, 1000000), - Event::ValidatorChosen(7, 1, 100), - Event::ValidatorChosen(7, 2, 90), - Event::ValidatorChosen(7, 3, 80), - Event::ValidatorChosen(7, 4, 70), - Event::ValidatorChosen(7, 5, 60), - Event::NewSession(30, 7, 5, 400), - ]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - // Session-7 - set block author as 1 & 2 for all blocks - set_author(6, 1, 60); - set_author(6, 2, 40); - mock::mint_rewards(1_000_000); - - mock::start_active_session(7); - - let mut new2 = vec![ - Event::StakeReward(1, 600000), - Event::StakeReward(2, 400000), - Event::ValidatorChosen(8, 1, 100), - Event::ValidatorChosen(8, 2, 90), - Event::ValidatorChosen(8, 3, 80), - Event::ValidatorChosen(8, 4, 70), - Event::ValidatorChosen(8, 5, 60), - Event::NewSession(35, 8, 5, 400), - ]; - expected.append(&mut new2); - assert_eq!(events(), expected); - - // Session-8 - each validator produces 1 block this round - set_author(7, 1, 20); - set_author(7, 2, 20); - set_author(7, 3, 20); - set_author(7, 4, 20); - set_author(7, 5, 20); - mock::mint_rewards(1_000_000); - - mock::start_active_session(8); - - let mut new3 = vec![ - Event::StakeReward(5, 200000), - Event::StakeReward(3, 200000), - Event::StakeReward(4, 200000), - Event::StakeReward(1, 200000), - Event::StakeReward(2, 200000), - Event::ValidatorChosen(9, 1, 100), - Event::ValidatorChosen(9, 2, 90), - Event::ValidatorChosen(9, 3, 80), - Event::ValidatorChosen(9, 4, 70), - Event::ValidatorChosen(9, 5, 60), - Event::NewSession(40, 9, 5, 400), - ]; - expected.append(&mut new3); - assert_eq!(events(), expected); - - assert!(NodleStaking::awarded_pts(2, 1).is_zero()); - assert!(NodleStaking::awarded_pts(3, 1).is_zero()); - assert!(NodleStaking::awarded_pts(3, 2).is_zero()); - assert!(NodleStaking::awarded_pts(4, 1).is_zero()); - assert!(NodleStaking::awarded_pts(4, 2).is_zero()); - assert!(NodleStaking::awarded_pts(4, 3).is_zero()); - assert!(NodleStaking::awarded_pts(4, 4).is_zero()); - assert!(NodleStaking::awarded_pts(4, 5).is_zero()); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(1))); - - let mut new4 = vec![Event::Rewarded(1, 1800000)]; - expected.append(&mut new4); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&1), (1801000, 100)); - assert_eq!(Balances::total_balance(&1), 1801000); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(2))); - - let mut new5 = vec![Event::Rewarded(2, 600000)]; - expected.append(&mut new5); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&2), (601000, 90)); - assert_eq!(Balances::total_balance(&2), 601000); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(3))); - - let mut new6 = vec![Event::Rewarded(3, 200000)]; - expected.append(&mut new6); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&3), (201000, 80)); - assert_eq!(Balances::total_balance(&3), 201000); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(4))); - - let mut new7 = vec![Event::Rewarded(4, 200000)]; - expected.append(&mut new7); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&4), (201000, 70)); - assert_eq!(Balances::total_balance(&4), 201000); - - assert_eq!( - NodleStaking::stake_rewards(&5), - [StakeReward { - session_idx: 7, - value: 200000, - }] - ); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(5))); - - assert_eq!(NodleStaking::stake_rewards(&5), []); - - let mut new8 = vec![Event::Rewarded(5, 200000)]; - expected.append(&mut new8); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&5), (201000, 60)); - assert_eq!(Balances::total_balance(&5), 201000); - - assert_eq!(NodleStaking::stake_rewards(&6), []); - - assert_noop!( - NodleStaking::withdraw_staking_rewards(Origin::signed(6)), - Error::::RewardsDNE, - ); - - ::StakeRewards::mutate(&6, |rewards| { - rewards.push(StakeReward { - session_idx: 7, - value: 1, - }) - }); - - assert_eq!( - NodleStaking::stake_rewards(&6), - [StakeReward { - session_idx: 7, - value: 1, - }] - ); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(6)),); - - let mut new9 = vec![Event::Rewarded(6, 0)]; - expected.append(&mut new9); - assert_eq!(events(), expected); - - assert_eq!( - NodleStaking::stake_rewards(&6), - [StakeReward { - session_idx: 7, - value: 1, - }] - ); - - mock::start_active_session(8); - - ::StakeRewards::mutate(&6, |rewards| { - rewards.push(StakeReward { - session_idx: 8, - value: 2, - }) - }); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(6)),); - - let mut new10 = vec![Event::Rewarded(6, 0)]; - expected.append(&mut new10); - assert_eq!(events(), expected); - - assert_eq!( - NodleStaking::stake_rewards(&6), - [ - StakeReward { - session_idx: 7, - value: 1, - }, - StakeReward { - session_idx: 8, - value: 2, - } - ] - ); - - mock::start_active_session(9); - - ::StakeRewards::mutate(&6, |rewards| { - rewards.push(StakeReward { - session_idx: 9, - value: 1, - }) - }); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(6)),); - - let mut new11 = vec![ - Event::ValidatorChosen(10, 1, 100), - Event::ValidatorChosen(10, 2, 90), - Event::ValidatorChosen(10, 3, 80), - Event::ValidatorChosen(10, 4, 70), - Event::ValidatorChosen(10, 5, 60), - Event::NewSession(45, 10, 5, 400), - Event::Rewarded(6, 4), - ]; - expected.append(&mut new11); - assert_eq!(events(), expected); - - assert_eq!(NodleStaking::stake_rewards(&6), []); - - assert_eq!(mock::balances(&6), (1004, 50)); - assert_eq!(Balances::total_balance(&6), 1004); - - assert_eq!(NodleStaking::total(), 450); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - }); -} - -#[test] -fn validator_commission() { - ExtBuilder::default() - .with_balances(vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)]) - .with_validators(vec![(1, 20)]) - .with_nominators(vec![(2, 1, 10), (3, 1, 10)]) - .tst_staking_build() - .execute_with(|| { - mock::start_active_session(4); - - let mut expected = vec![ - Event::ValidatorChosen(2, 1, 40), - Event::NewSession(5, 2, 1, 40), - Event::ValidatorChosen(3, 1, 40), - Event::NewSession(10, 3, 1, 40), - Event::ValidatorChosen(4, 1, 40), - Event::NewSession(15, 4, 1, 40), - Event::ValidatorChosen(5, 1, 40), - Event::NewSession(20, 5, 1, 40), - ]; - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&1), (100, 20)); - assert_eq!(Balances::total_balance(&1), 100); - - assert_eq!(mock::balances(&2), (100, 10)); - assert_eq!(Balances::total_balance(&2), 100); - - assert_eq!(mock::balances(&3), (100, 10)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 0)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&5), (100, 0)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 0)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(NodleStaking::total(), 40); - - assert_ok!(NodleStaking::validator_join_pool(Origin::signed(4), 20u128)); - assert_eq!( - last_event(), - MetaEvent::NodleStaking(Event::JoinedValidatorPool(4, 20u128, 60u128)) - ); - - assert_ok!(Session::set_keys(Origin::signed(4), UintAuthorityId(4).into(), vec![])); - - mock::start_active_session(5); - - let mut new1 = vec![ - Event::JoinedValidatorPool(4, 20, 60), - Event::ValidatorChosen(6, 1, 40), - Event::ValidatorChosen(6, 4, 20), - Event::NewSession(25, 6, 2, 60), - ]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(5), 4, 10, false)); - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(6), 4, 10, false)); - - let mut new2 = vec![Event::Nomination(5, 10, 4, 30), Event::Nomination(6, 10, 4, 40)]; - expected.append(&mut new2); - assert_eq!(events(), expected); - - mock::start_active_session(6); - - let mut new3 = vec![ - Event::ValidatorChosen(7, 1, 40), - Event::ValidatorChosen(7, 4, 40), - Event::NewSession(30, 7, 2, 80), - ]; - expected.append(&mut new3); - assert_eq!(events(), expected); - - mock::start_active_session(7); - - set_author(7, 1, 50); - set_author(7, 4, 50); - mock::mint_rewards(1_000_000); - - mock::start_active_session(8); - - let mut new4 = vec![ - Event::ValidatorChosen(8, 1, 40), - Event::ValidatorChosen(8, 4, 40), - Event::NewSession(35, 8, 2, 80), - Event::StakeReward(4, 300000), - Event::StakeReward(5, 100000), - Event::StakeReward(6, 100000), - Event::StakeReward(1, 300000), - Event::StakeReward(2, 100000), - Event::StakeReward(3, 100000), - Event::ValidatorChosen(9, 1, 40), - Event::ValidatorChosen(9, 4, 40), - Event::NewSession(40, 9, 2, 80), - ]; - expected.append(&mut new4); - assert_eq!(events(), expected); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(1))); - - let mut new5 = vec![Event::Rewarded(1, 300000)]; - expected.append(&mut new5); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&1), (300100, 20)); - assert_eq!(Balances::total_balance(&1), 300100); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(2))); - - let mut new6 = vec![Event::Rewarded(2, 100000)]; - expected.append(&mut new6); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&2), (100100, 10)); - assert_eq!(Balances::total_balance(&2), 100100); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(3))); - - let mut new7 = vec![Event::Rewarded(3, 100000)]; - expected.append(&mut new7); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&3), (100100, 10)); - assert_eq!(Balances::total_balance(&3), 100100); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(4))); - - let mut new8 = vec![Event::Rewarded(4, 300000)]; - expected.append(&mut new8); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&4), (300100, 20)); - assert_eq!(Balances::total_balance(&4), 300100); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(5))); - - let mut new9 = vec![Event::Rewarded(5, 100000)]; - expected.append(&mut new9); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&5), (100100, 10)); - assert_eq!(Balances::total_balance(&5), 100100); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(6))); - - let mut new10 = vec![Event::Rewarded(6, 100000)]; - expected.append(&mut new10); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&6), (100100, 10)); - assert_eq!(Balances::total_balance(&6), 100100); - - assert_eq!(NodleStaking::total(), 80); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events(),); - }); -} - -#[test] -fn multiple_nominations() { - ExtBuilder::default() - .with_balances(vec![ - (1, 100), - (2, 100), - (3, 100), - (4, 100), - (5, 100), - (6, 100), - (7, 100), - (8, 100), - (9, 100), - (10, 100), - ]) - .with_validators(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)]) - .with_nominators(vec![(6, 1, 10), (7, 1, 10), (8, 2, 10), (9, 2, 10), (10, 1, 10)]) - .tst_staking_build() - .execute_with(|| { - mock::start_active_session(4); - - // chooses top TotalSelectedCandidates (5), in order - let mut expected = vec![ - Event::ValidatorChosen(2, 1, 50), - Event::ValidatorChosen(2, 2, 40), - Event::ValidatorChosen(2, 3, 20), - Event::ValidatorChosen(2, 4, 20), - Event::ValidatorChosen(2, 5, 10), - Event::NewSession(5, 2, 5, 140), - Event::ValidatorChosen(3, 1, 50), - Event::ValidatorChosen(3, 2, 40), - Event::ValidatorChosen(3, 3, 20), - Event::ValidatorChosen(3, 4, 20), - Event::ValidatorChosen(3, 5, 10), - Event::NewSession(10, 3, 5, 140), - Event::ValidatorChosen(4, 1, 50), - Event::ValidatorChosen(4, 2, 40), - Event::ValidatorChosen(4, 3, 20), - Event::ValidatorChosen(4, 4, 20), - Event::ValidatorChosen(4, 5, 10), - Event::NewSession(15, 4, 5, 140), - Event::ValidatorChosen(5, 1, 50), - Event::ValidatorChosen(5, 2, 40), - Event::ValidatorChosen(5, 3, 20), - Event::ValidatorChosen(5, 4, 20), - Event::ValidatorChosen(5, 5, 10), - Event::NewSession(20, 5, 5, 140), - ]; - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&1), (100, 20)); - assert_eq!(Balances::total_balance(&1), 100); - - assert_eq!(mock::balances(&2), (100, 20)); - assert_eq!(Balances::total_balance(&2), 100); - - assert_eq!(mock::balances(&3), (100, 20)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 20)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&5), (100, 10)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 10)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(mock::balances(&7), (100, 10)); - assert_eq!(Balances::total_balance(&7), 100); - - assert_eq!(mock::balances(&8), (100, 10)); - assert_eq!(Balances::total_balance(&8), 100); - - assert_eq!(mock::balances(&9), (100, 10)); - assert_eq!(Balances::total_balance(&9), 100); - - assert_eq!(mock::balances(&10), (100, 10)); - assert_eq!(Balances::total_balance(&10), 100); - - assert_eq!(NodleStaking::total(), 140); - - assert_eq!(System::consumers(&1), 2); - assert_eq!(System::consumers(&2), 2); - assert_eq!(System::consumers(&3), 2); - assert_eq!(System::consumers(&4), 2); - assert_eq!(System::consumers(&5), 2); - assert_eq!(System::consumers(&6), 1); - assert_eq!(System::consumers(&7), 1); - assert_eq!(System::consumers(&8), 1); - assert_eq!(System::consumers(&9), 1); - assert_eq!(System::consumers(&10), 1); - - assert_noop!( - NodleStaking::nominator_nominate(Origin::signed(6), 1, 10, false), - Error::::AlreadyNominatedValidator, - ); - - assert_noop!( - NodleStaking::nominator_nominate(Origin::signed(6), 2, 2, false), - Error::::NominationBelowMin, - ); - - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(6), 2, 10, false)); - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(6), 3, 10, false)); - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(6), 4, 10, false)); - - assert_noop!( - NodleStaking::nominator_nominate(Origin::signed(6), 5, 10, false), - Error::::ExceedMaxValidatorPerNom, - ); - - let mut new1 = vec![ - Event::Nomination(6, 10, 2, 50), - Event::Nomination(6, 10, 3, 30), - Event::Nomination(6, 10, 4, 30), - ]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - mock::start_active_session(5); - - let mut new2 = vec![ - Event::ValidatorChosen(6, 1, 50), - Event::ValidatorChosen(6, 2, 50), - Event::ValidatorChosen(6, 3, 30), - Event::ValidatorChosen(6, 4, 30), - Event::ValidatorChosen(6, 5, 10), - Event::NewSession(25, 6, 5, 170), - ]; - - expected.append(&mut new2); - assert_eq!(events(), expected); - - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(7), 2, 80, false)); - assert_noop!( - NodleStaking::nominator_nominate(Origin::signed(7), 3, 11, false), - Error::::InsufficientBalance - ); - - assert_noop!( - NodleStaking::nominator_nominate(Origin::signed(10), 2, 10, false), - Error::::TooManyNominators - ); - - mock::start_active_session(6); - - let mut new3 = vec![ - Event::Nomination(7, 80, 2, 130), - Event::ValidatorChosen(7, 1, 50), - Event::ValidatorChosen(7, 2, 130), - Event::ValidatorChosen(7, 3, 30), - Event::ValidatorChosen(7, 4, 30), - Event::ValidatorChosen(7, 5, 10), - Event::NewSession(30, 7, 5, 250), - ]; - - expected.append(&mut new3); - assert_eq!(events(), expected); - - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(2))); - - assert_eq!( - last_event(), - MetaEvent::NodleStaking(Event::ValidatorScheduledExit(6, 2, 8)) - ); - - mock::start_active_session(7); - - let mut new4 = vec![ - Event::ValidatorScheduledExit(6, 2, 8), - Event::ValidatorChosen(8, 1, 50), - Event::ValidatorChosen(8, 3, 30), - Event::ValidatorChosen(8, 4, 30), - Event::ValidatorChosen(8, 5, 10), - Event::NewSession(35, 8, 4, 120), - ]; - - expected.append(&mut new4); - assert_eq!(events(), expected); - - // verify that nominations are removed after validator leaves, not before - assert_eq!(NodleStaking::nominator_state(7).unwrap().total, 90); - assert_eq!(NodleStaking::nominator_state(7).unwrap().nominations.0.len(), 2usize); - - assert_eq!(NodleStaking::nominator_state(6).unwrap().total, 40); - assert_eq!(NodleStaking::nominator_state(6).unwrap().nominations.0.len(), 4usize); - - assert_eq!( - NodleStaking::validator_state(&2).unwrap().nominators.0, - vec![ - Bond { owner: 6, amount: 10 }, - Bond { owner: 7, amount: 80 }, - Bond { owner: 8, amount: 10 }, - Bond { owner: 9, amount: 10 }, - ], - ); - - assert_eq!(mock::balances(&6), (100, 40)); - assert_eq!(mock::balances(&7), (100, 90)); - assert_eq!(mock::balances(&8), (100, 10)); - assert_eq!(mock::balances(&9), (100, 10)); - - assert_eq!(NodleStaking::total(), 250); - - mock::start_active_session(8); - - let mut new5 = vec![ - Event::NominatorLeftValidator(6, 2, 10, 0), - Event::NominatorLeftValidator(7, 2, 80, 0), - Event::NominatorLeftValidator(8, 2, 10, 0), - Event::NominatorLeftValidator(9, 2, 10, 0), - Event::ValidatorLeft(2, 130, 120), - Event::ValidatorChosen(9, 1, 50), - Event::ValidatorChosen(9, 3, 30), - Event::ValidatorChosen(9, 4, 30), - Event::ValidatorChosen(9, 5, 10), - Event::NewSession(40, 9, 4, 120), - ]; - - expected.append(&mut new5); - assert_eq!(events(), expected); - - assert_eq!(System::consumers(&2), 1); - - assert_eq!(System::consumers(&6), 1); - assert_eq!(System::consumers(&7), 1); - assert_eq!(System::consumers(&8), 1); - assert_eq!(System::consumers(&9), 1); - assert_eq!(System::consumers(&10), 1); - - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(6))); - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(7))); - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(8))); - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(9))); - - let mut new6 = vec![ - Event::Withdrawn(6, 10), - Event::Withdrawn(7, 80), - Event::Withdrawn(8, 10), - Event::NominatorLeft(8, 10), - Event::Withdrawn(9, 10), - Event::NominatorLeft(9, 10), - ]; - - expected.append(&mut new6); - assert_eq!(events(), expected); - - assert_eq!(System::consumers(&6), 1); - assert_eq!(System::consumers(&7), 1); - assert_eq!(System::consumers(&8), 0); - assert_eq!(System::consumers(&9), 0); - assert_eq!(System::consumers(&10), 1); - - assert_eq!(NodleStaking::nominator_state(6).unwrap().total, 30); - assert_eq!(NodleStaking::nominator_state(7).unwrap().total, 10); - - assert_eq!(NodleStaking::nominator_state(6).unwrap().nominations.0.len(), 3usize); - assert_eq!(NodleStaking::nominator_state(7).unwrap().nominations.0.len(), 1usize); - - // Group-2 - assert_eq!(mock::balances(&2), (100, 0)); - assert_eq!(Balances::total_balance(&2), 100); - - assert_eq!(mock::balances(&6), (100, 30)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(mock::balances(&7), (100, 10)); - assert_eq!(Balances::total_balance(&7), 100); - - assert_eq!(mock::balances(&8), (100, 0)); - assert_eq!(Balances::total_balance(&8), 100); - - assert_eq!(mock::balances(&9), (100, 0)); - assert_eq!(Balances::total_balance(&9), 100); - - // Group-1 - assert_eq!(mock::balances(&1), (100, 20)); - assert_eq!(Balances::total_balance(&1), 100); - - assert_eq!(mock::balances(&3), (100, 20)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 20)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&5), (100, 10)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&10), (100, 10)); - assert_eq!(Balances::total_balance(&10), 100); - - assert_eq!(NodleStaking::total(), 120); - - assert_eq!(System::consumers(&1), 2); - assert_eq!(System::consumers(&2), 1); - assert_eq!(System::consumers(&3), 2); - assert_eq!(System::consumers(&4), 2); - assert_eq!(System::consumers(&5), 2); - assert_eq!(System::consumers(&6), 1); - assert_eq!(System::consumers(&7), 1); - assert_eq!(System::consumers(&8), 0); - assert_eq!(System::consumers(&9), 0); - assert_eq!(System::consumers(&10), 1); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - }); -} - -#[test] -fn switch_nomination_works() { - ExtBuilder::default() - .with_balances(vec![ - (1, 100), - (2, 100), - (3, 100), - (4, 100), - (5, 100), - (6, 100), - (7, 100), - (8, 100), - (9, 100), - (10, 100), - ]) - .with_validators(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)]) - .with_nominators(vec![(6, 1, 10), (7, 1, 10), (8, 2, 10), (9, 2, 10), (10, 1, 10)]) - .tst_staking_build() - .execute_with(|| { - mock::start_active_session(4); - - // chooses top TotalSelectedCandidates (5), in order - let mut expected = vec![ - Event::ValidatorChosen(2, 1, 50), - Event::ValidatorChosen(2, 2, 40), - Event::ValidatorChosen(2, 3, 20), - Event::ValidatorChosen(2, 4, 20), - Event::ValidatorChosen(2, 5, 10), - Event::NewSession(5, 2, 5, 140), - Event::ValidatorChosen(3, 1, 50), - Event::ValidatorChosen(3, 2, 40), - Event::ValidatorChosen(3, 3, 20), - Event::ValidatorChosen(3, 4, 20), - Event::ValidatorChosen(3, 5, 10), - Event::NewSession(10, 3, 5, 140), - Event::ValidatorChosen(4, 1, 50), - Event::ValidatorChosen(4, 2, 40), - Event::ValidatorChosen(4, 3, 20), - Event::ValidatorChosen(4, 4, 20), - Event::ValidatorChosen(4, 5, 10), - Event::NewSession(15, 4, 5, 140), - Event::ValidatorChosen(5, 1, 50), - Event::ValidatorChosen(5, 2, 40), - Event::ValidatorChosen(5, 3, 20), - Event::ValidatorChosen(5, 4, 20), - Event::ValidatorChosen(5, 5, 10), - Event::NewSession(20, 5, 5, 140), - ]; - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&1), (100, 20)); - assert_eq!(Balances::total_balance(&1), 100); - - assert_eq!(mock::balances(&2), (100, 20)); - assert_eq!(Balances::total_balance(&2), 100); - - assert_eq!(mock::balances(&3), (100, 20)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 20)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&5), (100, 10)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 10)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(mock::balances(&7), (100, 10)); - assert_eq!(Balances::total_balance(&7), 100); - - assert_eq!(mock::balances(&8), (100, 10)); - assert_eq!(Balances::total_balance(&8), 100); - - assert_eq!(mock::balances(&9), (100, 10)); - assert_eq!(Balances::total_balance(&9), 100); - - assert_eq!(mock::balances(&10), (100, 10)); - assert_eq!(Balances::total_balance(&10), 100); - - assert_eq!(NodleStaking::total(), 140); - - assert_eq!(System::consumers(&1), 2); - assert_eq!(System::consumers(&2), 2); - assert_eq!(System::consumers(&3), 2); - assert_eq!(System::consumers(&4), 2); - assert_eq!(System::consumers(&5), 2); - assert_eq!(System::consumers(&6), 1); - assert_eq!(System::consumers(&7), 1); - assert_eq!(System::consumers(&8), 1); - assert_eq!(System::consumers(&9), 1); - assert_eq!(System::consumers(&10), 1); - - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(6), 2, 10, false)); - - let mut new1 = vec![Event::Nomination(6, 10, 2, 50)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!( - NodleStaking::nominator_state(6).unwrap().nominations, - OrderedSet::from([Bond { owner: 1, amount: 10 }, Bond { owner: 2, amount: 10 },].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(6).unwrap().total, 20); - assert_eq!(NodleStaking::nominator_state(6).unwrap().active_bond, 20); - assert_eq!(NodleStaking::total(), 150); - - // Check with invalid arguments - assert_noop!( - NodleStaking::nominator_move_nomination(Origin::signed(6), 2, 2, 5, false), - Error::::ValidatorDNE, - ); - - assert_noop!( - NodleStaking::nominator_move_nomination(Origin::signed(6), 2, 7, 5, false), - Error::::ValidatorDNE, - ); - - assert_noop!( - NodleStaking::nominator_move_nomination(Origin::signed(6), 7, 2, 5, false), - Error::::ValidatorDNE, - ); - - assert_noop!( - NodleStaking::nominator_move_nomination(Origin::signed(1), 2, 1, 5, false), - Error::::NominatorDNE, - ); - - assert_ok!(NodleStaking::nominator_move_nomination( - Origin::signed(6), - 2, - 1, - 0, - false, - )); - - let mut new2 = vec![ - Event::NominatorLeftValidator(6, 2, 10, 40), - Event::NominationMoved(6, 20, 2, 40, 1, 60), - ]; - - expected.append(&mut new2); - assert_eq!(events(), expected); - - assert_eq!( - NodleStaking::nominator_state(6).unwrap().nominations, - OrderedSet::from([Bond { owner: 1, amount: 20 }].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(6).unwrap().total, 20); - assert_eq!(NodleStaking::nominator_state(6).unwrap().active_bond, 20); - assert_eq!(NodleStaking::total(), 150); - - assert_noop!( - NodleStaking::nominator_move_nomination(Origin::signed(1), 2, 1, 5, false), - Error::::NominatorDNE, - ); - - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(6), 2, 10, false)); - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(6), 3, 10, false)); - - let mut new3 = vec![Event::Nomination(6, 10, 2, 50), Event::Nomination(6, 10, 3, 30)]; - - expected.append(&mut new3); - assert_eq!(events(), expected); - - assert_eq!( - NodleStaking::nominator_state(6).unwrap().nominations, - OrderedSet::from( - [ - Bond { owner: 1, amount: 20 }, - Bond { owner: 2, amount: 10 }, - Bond { owner: 3, amount: 10 }, - ] - .to_vec() - ), - ); - assert_eq!(NodleStaking::nominator_state(6).unwrap().total, 40); - assert_eq!(NodleStaking::nominator_state(6).unwrap().active_bond, 40); - assert_eq!(NodleStaking::total(), 170); - - assert_ok!(NodleStaking::nominator_move_nomination( - Origin::signed(6), - 3, - 4, - 5, - false, - )); - - assert_eq!(NodleStaking::total(), 175); - - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(6), 5, 10, false)); - - assert_ok!(NodleStaking::nominator_move_nomination( - Origin::signed(6), - 2, - 5, - 0, - false, - )); - - let mut new4 = vec![ - Event::NominatorLeftValidator(6, 3, 10, 20), - Event::NominationMoved(6, 45, 3, 20, 4, 35), - Event::Nomination(6, 10, 5, 20), - Event::NominatorLeftValidator(6, 2, 10, 40), - Event::NominationMoved(6, 55, 2, 40, 5, 30), - ]; - - expected.append(&mut new4); - assert_eq!(events(), expected); - - assert_eq!( - NodleStaking::nominator_state(6).unwrap().nominations, - OrderedSet::from( - [ - Bond { owner: 1, amount: 20 }, - Bond { owner: 4, amount: 15 }, - Bond { owner: 5, amount: 20 }, - ] - .to_vec() - ), - ); - assert_eq!(NodleStaking::nominator_state(6).unwrap().total, 55); - assert_eq!(NodleStaking::nominator_state(6).unwrap().active_bond, 55); - assert_eq!(NodleStaking::total(), 185); - - assert_eq!(mock::balances(&1), (100, 20)); - assert_eq!(Balances::total_balance(&1), 100); - - assert_eq!(mock::balances(&2), (100, 20)); - assert_eq!(Balances::total_balance(&2), 100); - - assert_eq!(mock::balances(&3), (100, 20)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 20)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&5), (100, 10)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 55)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(System::consumers(&1), 2); - assert_eq!(System::consumers(&2), 2); - assert_eq!(System::consumers(&3), 2); - assert_eq!(System::consumers(&4), 2); - assert_eq!(System::consumers(&5), 2); - assert_eq!(System::consumers(&6), 1); - assert_eq!(System::consumers(&7), 1); - assert_eq!(System::consumers(&8), 1); - assert_eq!(System::consumers(&9), 1); - assert_eq!(System::consumers(&10), 1); - }); -} - -#[test] -fn reconciliation_basics_works() { - ExtBuilder::default() - .with_balances(vec![ - (1, 100), - (2, 100), - (3, 100), - (4, 100), - (5, 100), - (6, 100), - (7, 100), - (8, 100), - (9, 100), - (10, 100), - ]) - .with_validators(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)]) - .with_nominators(vec![(6, 1, 10), (7, 1, 10), (8, 2, 10), (9, 2, 10), (10, 1, 10)]) - .tst_staking_build() - .execute_with(|| { - mock::start_active_session(4); - - // chooses top TotalSelectedCandidates (5), in order - let mut expected = vec![ - Event::ValidatorChosen(2, 1, 50), - Event::ValidatorChosen(2, 2, 40), - Event::ValidatorChosen(2, 3, 20), - Event::ValidatorChosen(2, 4, 20), - Event::ValidatorChosen(2, 5, 10), - Event::NewSession(5, 2, 5, 140), - Event::ValidatorChosen(3, 1, 50), - Event::ValidatorChosen(3, 2, 40), - Event::ValidatorChosen(3, 3, 20), - Event::ValidatorChosen(3, 4, 20), - Event::ValidatorChosen(3, 5, 10), - Event::NewSession(10, 3, 5, 140), - Event::ValidatorChosen(4, 1, 50), - Event::ValidatorChosen(4, 2, 40), - Event::ValidatorChosen(4, 3, 20), - Event::ValidatorChosen(4, 4, 20), - Event::ValidatorChosen(4, 5, 10), - Event::NewSession(15, 4, 5, 140), - Event::ValidatorChosen(5, 1, 50), - Event::ValidatorChosen(5, 2, 40), - Event::ValidatorChosen(5, 3, 20), - Event::ValidatorChosen(5, 4, 20), - Event::ValidatorChosen(5, 5, 10), - Event::NewSession(20, 5, 5, 140), - ]; - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&1), (100, 20)); - assert_eq!(Balances::total_balance(&1), 100); - - assert_eq!(mock::balances(&2), (100, 20)); - assert_eq!(Balances::total_balance(&2), 100); - - assert_eq!(mock::balances(&3), (100, 20)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 20)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&5), (100, 10)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 10)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(mock::balances(&7), (100, 10)); - assert_eq!(Balances::total_balance(&7), 100); - - assert_eq!(mock::balances(&8), (100, 10)); - assert_eq!(Balances::total_balance(&8), 100); - - assert_eq!(mock::balances(&9), (100, 10)); - assert_eq!(Balances::total_balance(&9), 100); - - assert_eq!(mock::balances(&10), (100, 10)); - assert_eq!(Balances::total_balance(&10), 100); - - assert_eq!(NodleStaking::total(), 140); - - assert_eq!(System::consumers(&1), 2); - assert_eq!(System::consumers(&2), 2); - assert_eq!(System::consumers(&3), 2); - assert_eq!(System::consumers(&4), 2); - assert_eq!(System::consumers(&5), 2); - assert_eq!(System::consumers(&6), 1); - assert_eq!(System::consumers(&7), 1); - assert_eq!(System::consumers(&8), 1); - assert_eq!(System::consumers(&9), 1); - assert_eq!(System::consumers(&10), 1); - - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(6), 2, 40, false)); - - assert_eq!(mock::balances(&6), (100, 50)); - assert_eq!(Balances::total_balance(&6), 100); - - let mut new1 = vec![Event::Nomination(6, 40, 2, 80)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!( - NodleStaking::nominator_state(6).unwrap().nominations, - OrderedSet::from([Bond { owner: 1, amount: 10 }, Bond { owner: 2, amount: 40 },].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(6).unwrap().total, 50); - assert_eq!(NodleStaking::nominator_state(6).unwrap().active_bond, 50); - assert_eq!(NodleStaking::total(), 180); - - let s1_max_validators = NodleStaking::staking_max_validators(); - let s1_min_stake = NodleStaking::staking_min_stake_session_selection(); - let s1_min_validator_bond = NodleStaking::staking_min_validator_bond(); - let s1_min_nomination_total_bond = NodleStaking::staking_min_nominator_total_bond(); - let s1_min_nomination_chill_threshold = NodleStaking::staking_min_nomination_chill_threshold(); - - let s1_new_min_nomination_total_bond = 25; - let s1_new_min_nomination_chill_threshold = 15; - - assert_noop!( - NodleStaking::set_staking_limits( - Origin::signed(CancelOrigin::get()), - 0, - s1_min_stake, - s1_min_validator_bond, - s1_new_min_nomination_total_bond, - s1_new_min_nomination_chill_threshold, - ), - Error::::InvalidArguments, - ); - - assert_noop!( - NodleStaking::set_staking_limits( - Origin::signed(CancelOrigin::get()), - s1_max_validators, - Zero::zero(), - s1_min_validator_bond, - s1_new_min_nomination_total_bond, - s1_new_min_nomination_chill_threshold, - ), - Error::::InvalidArguments, - ); - - assert_noop!( - NodleStaking::set_staking_limits( - Origin::signed(CancelOrigin::get()), - s1_max_validators, - s1_min_stake, - Zero::zero(), - s1_new_min_nomination_total_bond, - s1_new_min_nomination_chill_threshold, - ), - Error::::InvalidArguments, - ); - - assert_noop!( - NodleStaking::set_staking_limits( - Origin::signed(CancelOrigin::get()), - s1_max_validators, - s1_min_stake, - s1_min_validator_bond, - Zero::zero(), - s1_new_min_nomination_chill_threshold, - ), - Error::::InvalidArguments, - ); - - assert_noop!( - NodleStaking::set_staking_limits( - Origin::signed(CancelOrigin::get()), - s1_max_validators, - s1_min_stake, - s1_min_validator_bond, - s1_new_min_nomination_total_bond, - Zero::zero(), - ), - Error::::InvalidArguments, - ); - - assert_ok!(NodleStaking::set_staking_limits( - Origin::signed(CancelOrigin::get()), - s1_max_validators, - s1_min_stake, - s1_min_validator_bond, - s1_new_min_nomination_total_bond, - s1_new_min_nomination_chill_threshold, - )); - - let mut new1 = vec![ - Event::NewStakingLimits( - s1_max_validators, - s1_max_validators, - s1_min_validator_bond, - s1_min_validator_bond, - s1_min_stake, - s1_min_stake, - s1_min_nomination_total_bond, - s1_new_min_nomination_total_bond, - s1_min_nomination_chill_threshold, - s1_new_min_nomination_chill_threshold, - ), - Event::NominationBelowThreashold(6, 1, 10, 10, 40), - Event::NominationBelowThreashold(7, 1, 10, 10, 0), - Event::NominationBelowThreashold(10, 1, 10, 10, 0), - Event::NominationBelowThreashold(8, 2, 10, 10, 0), - Event::NominationBelowThreashold(9, 2, 10, 10, 0), - ]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(NodleStaking::total(), 180); - - assert_eq!( - NodleStaking::nominator_state(6).unwrap().nominations, - OrderedSet::from([Bond { owner: 2, amount: 40 },].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(6).unwrap().total, 50); - assert_eq!(NodleStaking::nominator_state(6).unwrap().active_bond, 40); - assert_eq!(NodleStaking::nominator_state(6).unwrap().frozen_bond, 10); - - assert_eq!( - NodleStaking::nominator_state(7).unwrap().nominations, - OrderedSet::from([].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(7).unwrap().total, 10); - assert_eq!(NodleStaking::nominator_state(7).unwrap().active_bond, 0); - assert_eq!(NodleStaking::nominator_state(7).unwrap().frozen_bond, 10); - - assert_eq!( - NodleStaking::nominator_state(10).unwrap().nominations, - OrderedSet::from([].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(10).unwrap().total, 10); - assert_eq!(NodleStaking::nominator_state(10).unwrap().active_bond, 0); - assert_eq!(NodleStaking::nominator_state(10).unwrap().frozen_bond, 10); - - assert_eq!( - NodleStaking::nominator_state(8).unwrap().nominations, - OrderedSet::from([].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(8).unwrap().total, 10); - assert_eq!(NodleStaking::nominator_state(8).unwrap().active_bond, 0); - assert_eq!(NodleStaking::nominator_state(8).unwrap().frozen_bond, 10); - - assert_eq!( - NodleStaking::nominator_state(9).unwrap().nominations, - OrderedSet::from([].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(9).unwrap().total, 10); - assert_eq!(NodleStaking::nominator_state(9).unwrap().active_bond, 0); - assert_eq!(NodleStaking::nominator_state(9).unwrap().frozen_bond, 10); - - assert_ok!(NodleStaking::unbond_frozen(Origin::signed(6))); - - let mut new1 = vec![Event::NominationUnbondFrozen(6, 10, 40, 40)]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&6), (100, 40)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(System::consumers(&6), 1); - - assert_ok!(NodleStaking::unbond_frozen(Origin::signed(7))); - - let mut new1 = vec![Event::NominationUnbondFrozen(7, 10, 0, 0), Event::NominatorLeft(7, 10)]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&7), (100, 0)); - assert_eq!(Balances::total_balance(&7), 100); - - assert_eq!(System::consumers(&7), 0); - - assert_noop!( - NodleStaking::nominator_bond_more(Origin::signed(7), 2, 50, false), - Error::::NominatorDNE, - ); - - assert_ok!(NodleStaking::unbond_frozen(Origin::signed(10))); - - let mut new1 = vec![ - Event::NominationUnbondFrozen(10, 10, 0, 0), - Event::NominatorLeft(10, 10), - ]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&10), (100, 0)); - assert_eq!(Balances::total_balance(&10), 100); - - assert_eq!(System::consumers(&10), 0); - - assert_noop!( - NodleStaking::nominator_bond_more(Origin::signed(10), 2, 50, false), - Error::::NominatorDNE, - ); - - assert_ok!(NodleStaking::unbond_frozen(Origin::signed(8))); - - let mut new1 = vec![Event::NominationUnbondFrozen(8, 10, 0, 0), Event::NominatorLeft(8, 10)]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&8), (100, 0)); - assert_eq!(Balances::total_balance(&8), 100); - - assert_eq!(System::consumers(&8), 0); - - assert_noop!( - NodleStaking::nominator_bond_more(Origin::signed(8), 2, 50, false), - Error::::NominatorDNE, - ); - - assert_ok!(NodleStaking::unbond_frozen(Origin::signed(9))); - - let mut new1 = vec![Event::NominationUnbondFrozen(9, 10, 0, 0), Event::NominatorLeft(9, 10)]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&9), (100, 0)); - assert_eq!(Balances::total_balance(&9), 100); - - assert_eq!(System::consumers(&9), 0); - - assert_noop!( - NodleStaking::nominator_bond_more(Origin::signed(9), 2, 50, false), - Error::::NominatorDNE, - ); - }); -} - -#[test] -fn unfreeze_bond_arg_flag_works() { - ExtBuilder::default() - .with_balances(vec![ - (1, 100), - (2, 100), - (3, 100), - (4, 100), - (5, 100), - (6, 100), - (7, 100), - (8, 100), - (9, 100), - (10, 100), - ]) - .with_validators(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)]) - .with_nominators(vec![(6, 1, 10), (7, 1, 10), (8, 2, 10), (9, 2, 10), (10, 1, 25)]) - .tst_staking_build() - .execute_with(|| { - mock::start_active_session(4); - - // chooses top TotalSelectedCandidates (5), in order - let mut expected = vec![ - Event::ValidatorChosen(2, 1, 65), - Event::ValidatorChosen(2, 2, 40), - Event::ValidatorChosen(2, 3, 20), - Event::ValidatorChosen(2, 4, 20), - Event::ValidatorChosen(2, 5, 10), - Event::NewSession(5, 2, 5, 155), - Event::ValidatorChosen(3, 1, 65), - Event::ValidatorChosen(3, 2, 40), - Event::ValidatorChosen(3, 3, 20), - Event::ValidatorChosen(3, 4, 20), - Event::ValidatorChosen(3, 5, 10), - Event::NewSession(10, 3, 5, 155), - Event::ValidatorChosen(4, 1, 65), - Event::ValidatorChosen(4, 2, 40), - Event::ValidatorChosen(4, 3, 20), - Event::ValidatorChosen(4, 4, 20), - Event::ValidatorChosen(4, 5, 10), - Event::NewSession(15, 4, 5, 155), - Event::ValidatorChosen(5, 1, 65), - Event::ValidatorChosen(5, 2, 40), - Event::ValidatorChosen(5, 3, 20), - Event::ValidatorChosen(5, 4, 20), - Event::ValidatorChosen(5, 5, 10), - Event::NewSession(20, 5, 5, 155), - ]; - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&1), (100, 20)); - assert_eq!(Balances::total_balance(&1), 100); - - assert_eq!(mock::balances(&2), (100, 20)); - assert_eq!(Balances::total_balance(&2), 100); - - assert_eq!(mock::balances(&3), (100, 20)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 20)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&5), (100, 10)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 10)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(mock::balances(&7), (100, 10)); - assert_eq!(Balances::total_balance(&7), 100); - - assert_eq!(mock::balances(&8), (100, 10)); - assert_eq!(Balances::total_balance(&8), 100); - - assert_eq!(mock::balances(&9), (100, 10)); - assert_eq!(Balances::total_balance(&9), 100); - - assert_eq!(mock::balances(&10), (100, 25)); - assert_eq!(Balances::total_balance(&10), 100); - - assert_eq!(NodleStaking::total(), 155); - - assert_eq!(System::consumers(&1), 2); - assert_eq!(System::consumers(&2), 2); - assert_eq!(System::consumers(&3), 2); - assert_eq!(System::consumers(&4), 2); - assert_eq!(System::consumers(&5), 2); - assert_eq!(System::consumers(&6), 1); - assert_eq!(System::consumers(&7), 1); - assert_eq!(System::consumers(&8), 1); - assert_eq!(System::consumers(&9), 1); - assert_eq!(System::consumers(&10), 1); - - assert_eq!( - NodleStaking::validator_state(1).unwrap().nominators, - OrderedSet::from( - [ - Bond { owner: 6, amount: 10 }, - Bond { owner: 7, amount: 10 }, - Bond { owner: 10, amount: 25 }, - ] - .to_vec() - ), - ); - - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(6), 3, 20, false)); - - assert_eq!(mock::balances(&6), (100, 30)); - assert_eq!(Balances::total_balance(&6), 100); - - let mut new1 = vec![Event::Nomination(6, 20, 3, 40)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!( - NodleStaking::validator_state(3).unwrap().nominators, - OrderedSet::from([Bond { owner: 6, amount: 20 },].to_vec()), - ); - - assert_eq!( - NodleStaking::nominator_state(6).unwrap().nominations, - OrderedSet::from([Bond { owner: 1, amount: 10 }, Bond { owner: 3, amount: 20 },].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(6).unwrap().total, 30); - assert_eq!(NodleStaking::nominator_state(6).unwrap().active_bond, 30); - assert_eq!(NodleStaking::total(), 175); - - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(7), 3, 20, true,)); - - assert_eq!(mock::balances(&7), (100, 30)); - assert_eq!(Balances::total_balance(&7), 100); - - let mut new1 = vec![Event::Nomination(7, 20, 3, 60)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!( - NodleStaking::nominator_state(7).unwrap().nominations, - OrderedSet::from([Bond { owner: 1, amount: 10 }, Bond { owner: 3, amount: 20 },].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(7).unwrap().total, 30); - assert_eq!(NodleStaking::nominator_state(7).unwrap().active_bond, 30); - assert_eq!(NodleStaking::total(), 195); - - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(8), 4, 20, true,)); - - assert_eq!(mock::balances(&8), (100, 30)); - assert_eq!(Balances::total_balance(&8), 100); - - let mut new1 = vec![Event::Nomination(8, 20, 4, 40)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!( - NodleStaking::nominator_state(8).unwrap().nominations, - OrderedSet::from([Bond { owner: 2, amount: 10 }, Bond { owner: 4, amount: 20 },].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(8).unwrap().total, 30); - assert_eq!(NodleStaking::nominator_state(8).unwrap().active_bond, 30); - assert_eq!(NodleStaking::total(), 215); - - let s1_max_validators = NodleStaking::staking_max_validators(); - let s1_min_stake = NodleStaking::staking_min_stake_session_selection(); - let s1_min_validator_bond = NodleStaking::staking_min_validator_bond(); - let s1_min_nomination_total_bond = NodleStaking::staking_min_nominator_total_bond(); - let s1_min_nomination_chill_threshold = NodleStaking::staking_min_nomination_chill_threshold(); - - let s1_new_min_nomination_total_bond = 25; - let s1_new_min_nomination_chill_threshold = 15; - - assert_ok!(NodleStaking::set_staking_limits( - Origin::signed(CancelOrigin::get()), - s1_max_validators, - s1_min_stake, - s1_min_validator_bond, - s1_new_min_nomination_total_bond, - s1_new_min_nomination_chill_threshold, - )); - - let mut new1 = vec![ - Event::NewStakingLimits( - s1_max_validators, - s1_max_validators, - s1_min_validator_bond, - s1_min_validator_bond, - s1_min_stake, - s1_min_stake, - s1_min_nomination_total_bond, - s1_new_min_nomination_total_bond, - s1_min_nomination_chill_threshold, - s1_new_min_nomination_chill_threshold, - ), - Event::NominationBelowThreashold(6, 1, 10, 10, 20), - Event::NominationBelowThreashold(7, 1, 10, 10, 20), - Event::NominationBelowThreashold(8, 2, 10, 10, 20), - Event::NominationBelowThreashold(9, 2, 10, 10, 0), - ]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!( - NodleStaking::validator_state(1).unwrap().nominators, - OrderedSet::from([Bond { owner: 10, amount: 25 },].to_vec()), - ); - - assert_eq!( - NodleStaking::nominator_state(6).unwrap().nominations, - OrderedSet::from([Bond { owner: 3, amount: 20 },].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(6).unwrap().total, 30); - assert_eq!(NodleStaking::nominator_state(6).unwrap().active_bond, 20); - assert_eq!(NodleStaking::nominator_state(6).unwrap().frozen_bond, 10); - - assert_eq!( - NodleStaking::nominator_state(7).unwrap().nominations, - OrderedSet::from([Bond { owner: 3, amount: 20 },].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(7).unwrap().total, 30); - assert_eq!(NodleStaking::nominator_state(7).unwrap().active_bond, 20); - assert_eq!(NodleStaking::nominator_state(7).unwrap().frozen_bond, 10); - - assert_eq!( - NodleStaking::nominator_state(8).unwrap().nominations, - OrderedSet::from([Bond { owner: 4, amount: 20 },].to_vec()), - ); - assert_eq!(NodleStaking::nominator_state(8).unwrap().total, 30); - assert_eq!(NodleStaking::nominator_state(8).unwrap().active_bond, 20); - assert_eq!(NodleStaking::nominator_state(8).unwrap().frozen_bond, 10); - - assert_noop!( - NodleStaking::nominator_nominate(Origin::signed(6), 1, 14, false), - Error::::NominationBelowMin, - ); - - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(6), 1, 14, true)); - - let mut new1 = vec![Event::Nomination(6, 24, 1, 69)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_ok!(NodleStaking::nominator_bond_more(Origin::signed(6), 1, 6, true),); - - let mut new1 = vec![Event::NominationIncreased(6, 30, 1, 69, 75)]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!( - NodleStaking::validator_state(1).unwrap().nominators, - OrderedSet::from([Bond { owner: 6, amount: 30 }, Bond { owner: 10, amount: 25 }].to_vec()), - ); - - assert_eq!(mock::balances(&1), (100, 20)); - assert_eq!(Balances::total_balance(&1), 100); - - assert_eq!(mock::balances(&2), (100, 20)); - assert_eq!(Balances::total_balance(&2), 100); - - assert_eq!(mock::balances(&3), (100, 20)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 20)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&5), (100, 10)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 60)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(mock::balances(&7), (100, 30)); - assert_eq!(Balances::total_balance(&7), 100); - - assert_eq!(mock::balances(&8), (100, 30)); - assert_eq!(Balances::total_balance(&8), 100); - - assert_eq!(mock::balances(&9), (100, 10)); - assert_eq!(Balances::total_balance(&9), 100); - - assert_eq!(mock::balances(&10), (100, 25)); - assert_eq!(Balances::total_balance(&10), 100); - - assert_eq!(NodleStaking::total(), 245); - - assert_eq!(System::consumers(&1), 2); - assert_eq!(System::consumers(&2), 2); - assert_eq!(System::consumers(&3), 2); - assert_eq!(System::consumers(&4), 2); - assert_eq!(System::consumers(&5), 2); - assert_eq!(System::consumers(&6), 1); - assert_eq!(System::consumers(&7), 1); - assert_eq!(System::consumers(&8), 1); - assert_eq!(System::consumers(&9), 1); - assert_eq!(System::consumers(&10), 1); - }); -} - -#[test] -fn validators_bond() { - ExtBuilder::default() - .with_balances(vec![ - (1, 100), - (2, 100), - (3, 100), - (4, 100), - (5, 100), - (6, 100), - (7, 100), - (8, 100), - (9, 100), - (10, 100), - (20, 1), - (30, 1), - ]) - .with_validators(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)]) - .with_nominators(vec![(6, 1, 10), (7, 1, 10), (8, 2, 10), (9, 2, 10), (10, 1, 10)]) - .tst_staking_build() - .execute_with(|| { - mock::start_active_session(4); - - let mut expected = vec![ - Event::ValidatorChosen(2, 1, 50), - Event::ValidatorChosen(2, 2, 40), - Event::ValidatorChosen(2, 3, 20), - Event::ValidatorChosen(2, 4, 20), - Event::ValidatorChosen(2, 5, 10), - Event::NewSession(5, 2, 5, 140), - Event::ValidatorChosen(3, 1, 50), - Event::ValidatorChosen(3, 2, 40), - Event::ValidatorChosen(3, 3, 20), - Event::ValidatorChosen(3, 4, 20), - Event::ValidatorChosen(3, 5, 10), - Event::NewSession(10, 3, 5, 140), - Event::ValidatorChosen(4, 1, 50), - Event::ValidatorChosen(4, 2, 40), - Event::ValidatorChosen(4, 3, 20), - Event::ValidatorChosen(4, 4, 20), - Event::ValidatorChosen(4, 5, 10), - Event::NewSession(15, 4, 5, 140), - Event::ValidatorChosen(5, 1, 50), - Event::ValidatorChosen(5, 2, 40), - Event::ValidatorChosen(5, 3, 20), - Event::ValidatorChosen(5, 4, 20), - Event::ValidatorChosen(5, 5, 10), - Event::NewSession(20, 5, 5, 140), - ]; - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&1), (100, 20)); - assert_eq!(Balances::total_balance(&1), 100); - - assert_eq!(mock::balances(&2), (100, 20)); - assert_eq!(Balances::total_balance(&2), 100); - - assert_eq!(mock::balances(&3), (100, 20)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 20)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&5), (100, 10)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 10)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(mock::balances(&7), (100, 10)); - assert_eq!(Balances::total_balance(&7), 100); - - assert_eq!(mock::balances(&8), (100, 10)); - assert_eq!(Balances::total_balance(&8), 100); - - assert_eq!(mock::balances(&9), (100, 10)); - assert_eq!(Balances::total_balance(&9), 100); - - assert_eq!(mock::balances(&10), (100, 10)); - assert_eq!(Balances::total_balance(&10), 100); - - assert_eq!(NodleStaking::total(), 140); - - assert_noop!( - NodleStaking::validator_bond_more(Origin::signed(6), 50), - Error::::ValidatorDNE - ); - - assert_ok!(NodleStaking::validator_bond_more(Origin::signed(1), 50)); - - assert_noop!( - NodleStaking::validator_bond_more(Origin::signed(1), 40), - Error::::InsufficientBalance - ); - - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(1))); - - let mut new1 = vec![ - Event::ValidatorBondedMore(1, 20, 70), - Event::ValidatorScheduledExit(4, 1, 6), - ]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - mock::start_active_session(5); - - assert_noop!( - NodleStaking::validator_bond_more(Origin::signed(1), 30), - Error::::CannotActivateIfLeaving, - ); - - mock::start_active_session(6); - - let mut new2 = vec![ - Event::ValidatorChosen(6, 2, 40), - Event::ValidatorChosen(6, 3, 20), - Event::ValidatorChosen(6, 4, 20), - Event::ValidatorChosen(6, 5, 10), - Event::NewSession(25, 6, 4, 90), - Event::NominatorLeftValidator(6, 1, 10, 0), - Event::NominatorLeftValidator(7, 1, 10, 0), - Event::NominatorLeftValidator(10, 1, 10, 0), - Event::ValidatorLeft(1, 100, 90), - Event::ValidatorChosen(7, 2, 40), - Event::ValidatorChosen(7, 3, 20), - Event::ValidatorChosen(7, 4, 20), - Event::ValidatorChosen(7, 5, 10), - Event::NewSession(30, 7, 4, 90), - ]; - expected.append(&mut new2); - assert_eq!(events(), expected); - - assert_noop!( - NodleStaking::validator_bond_more(Origin::signed(1), 40), - Error::::ValidatorDNE, - ); - - assert_ok!(NodleStaking::validator_bond_more(Origin::signed(2), 80)); - - assert_ok!(NodleStaking::validator_bond_less(Origin::signed(2), 90)); - - assert_ok!(NodleStaking::validator_bond_less(Origin::signed(3), 10)); - - let mut new3 = vec![ - Event::ValidatorBondedMore(2, 20, 100), - Event::ValidatorBondedLess(2, 100, 10), - Event::ValidatorBondedLess(3, 20, 10), - ]; - - expected.append(&mut new3); - assert_eq!(events(), expected); - - assert_noop!( - NodleStaking::validator_bond_less(Origin::signed(2), 11), - Error::::Underflow - ); - assert_noop!( - NodleStaking::validator_bond_less(Origin::signed(2), 1), - Error::::ValidatorBondBelowMin - ); - assert_noop!( - NodleStaking::validator_bond_less(Origin::signed(3), 1), - Error::::ValidatorBondBelowMin - ); - assert_noop!( - NodleStaking::validator_bond_less(Origin::signed(4), 11), - Error::::ValidatorBondBelowMin - ); - - assert_ok!(NodleStaking::validator_bond_less(Origin::signed(4), 10)); - - mock::start_active_session(9); - - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(2))); - assert_eq!(mock::balances(&2), (100, 10)); - - let mut new4 = vec![ - Event::ValidatorBondedLess(4, 20, 10), - Event::ValidatorChosen(8, 2, 30), - Event::ValidatorChosen(8, 3, 10), - Event::ValidatorChosen(8, 4, 10), - Event::ValidatorChosen(8, 5, 10), - Event::NewSession(35, 8, 4, 60), - Event::ValidatorChosen(9, 2, 30), - Event::ValidatorChosen(9, 3, 10), - Event::ValidatorChosen(9, 4, 10), - Event::ValidatorChosen(9, 5, 10), - Event::NewSession(40, 9, 4, 60), - Event::ValidatorChosen(10, 2, 30), - Event::ValidatorChosen(10, 3, 10), - Event::ValidatorChosen(10, 4, 10), - Event::ValidatorChosen(10, 5, 10), - Event::NewSession(45, 10, 4, 60), - Event::Withdrawn(2, 90), - ]; - expected.append(&mut new4); - assert_eq!(events(), expected); - }); -} - -#[test] -fn nominators_bond() { - ExtBuilder::default() - .with_balances(vec![ - (1, 100), - (2, 100), - (3, 100), - (4, 100), - (5, 100), - (6, 100), - (7, 100), - (8, 100), - (9, 100), - (10, 100), - ]) - .with_validators(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)]) - .with_nominators(vec![(6, 1, 10), (7, 1, 10), (8, 2, 10), (9, 2, 10), (10, 1, 10)]) - .tst_staking_build() - .execute_with(|| { - mock::start_active_session(4); - - let mut expected = vec![ - Event::ValidatorChosen(2, 1, 50), - Event::ValidatorChosen(2, 2, 40), - Event::ValidatorChosen(2, 3, 20), - Event::ValidatorChosen(2, 4, 20), - Event::ValidatorChosen(2, 5, 10), - Event::NewSession(5, 2, 5, 140), - Event::ValidatorChosen(3, 1, 50), - Event::ValidatorChosen(3, 2, 40), - Event::ValidatorChosen(3, 3, 20), - Event::ValidatorChosen(3, 4, 20), - Event::ValidatorChosen(3, 5, 10), - Event::NewSession(10, 3, 5, 140), - Event::ValidatorChosen(4, 1, 50), - Event::ValidatorChosen(4, 2, 40), - Event::ValidatorChosen(4, 3, 20), - Event::ValidatorChosen(4, 4, 20), - Event::ValidatorChosen(4, 5, 10), - Event::NewSession(15, 4, 5, 140), - Event::ValidatorChosen(5, 1, 50), - Event::ValidatorChosen(5, 2, 40), - Event::ValidatorChosen(5, 3, 20), - Event::ValidatorChosen(5, 4, 20), - Event::ValidatorChosen(5, 5, 10), - Event::NewSession(20, 5, 5, 140), - ]; - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&1), (100, 20)); - assert_eq!(Balances::total_balance(&1), 100); - - assert_eq!(mock::balances(&2), (100, 20)); - assert_eq!(Balances::total_balance(&2), 100); - - assert_eq!(mock::balances(&3), (100, 20)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 20)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&5), (100, 10)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 10)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(mock::balances(&7), (100, 10)); - assert_eq!(Balances::total_balance(&7), 100); - - assert_eq!(mock::balances(&8), (100, 10)); - assert_eq!(Balances::total_balance(&8), 100); - - assert_eq!(mock::balances(&9), (100, 10)); - assert_eq!(Balances::total_balance(&9), 100); - - assert_eq!(mock::balances(&10), (100, 10)); - assert_eq!(Balances::total_balance(&10), 100); - - assert_eq!(NodleStaking::total(), 140); - - assert_noop!( - NodleStaking::nominator_bond_more(Origin::signed(1), 2, 50, false), - Error::::NominatorDNE, - ); - assert_noop!( - NodleStaking::nominator_bond_more(Origin::signed(6), 2, 50, false), - Error::::NominationDNE, - ); - assert_noop!( - NodleStaking::nominator_bond_more(Origin::signed(7), 6, 50, false), - Error::::ValidatorDNE, - ); - assert_noop!( - NodleStaking::nominator_bond_less(Origin::signed(6), 1, 11), - Error::::Underflow, - ); - assert_noop!( - NodleStaking::nominator_bond_less(Origin::signed(6), 1, 8), - Error::::NominationBelowMin, - ); - assert_noop!( - NodleStaking::nominator_bond_less(Origin::signed(6), 1, 6), - Error::::NominatorBondBelowMin, - ); - - assert_ok!(NodleStaking::nominator_bond_more(Origin::signed(6), 1, 10, false)); - - assert_noop!( - NodleStaking::nominator_bond_less(Origin::signed(6), 2, 5), - Error::::NominationDNE, - ); - assert_noop!( - NodleStaking::nominator_bond_more(Origin::signed(6), 1, 81, false), - Error::::InsufficientBalance, - ); - - mock::start_active_session(5); - - assert_eq!(mock::balances(&6), (100, 20)); - assert_eq!(NodleStaking::total(), 150); - assert_ok!(NodleStaking::validator_exit_pool(Origin::signed(1))); - - let mut new1 = vec![ - Event::NominationIncreased(6, 20, 1, 50, 60), - Event::ValidatorChosen(6, 1, 60), - Event::ValidatorChosen(6, 2, 40), - Event::ValidatorChosen(6, 3, 20), - Event::ValidatorChosen(6, 4, 20), - Event::ValidatorChosen(6, 5, 10), - Event::NewSession(25, 6, 5, 150), - Event::ValidatorScheduledExit(5, 1, 7), - ]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - mock::start_active_session(7); - - let mut new2 = vec![ - Event::ValidatorChosen(7, 2, 40), - Event::ValidatorChosen(7, 3, 20), - Event::ValidatorChosen(7, 4, 20), - Event::ValidatorChosen(7, 5, 10), - Event::NewSession(30, 7, 4, 90), - Event::NominatorLeftValidator(6, 1, 20, 0), - Event::NominatorLeftValidator(7, 1, 10, 0), - Event::NominatorLeftValidator(10, 1, 10, 0), - Event::ValidatorLeft(1, 60, 90), - Event::ValidatorChosen(8, 2, 40), - Event::ValidatorChosen(8, 3, 20), - Event::ValidatorChosen(8, 4, 20), - Event::ValidatorChosen(8, 5, 10), - Event::NewSession(35, 8, 4, 90), - ]; - - expected.append(&mut new2); - assert_eq!(events(), expected); - - mock::start_active_session(8); - - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(6))); - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(7))); - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(10))); - - let mut new3 = vec![ - Event::ValidatorChosen(9, 2, 40), - Event::ValidatorChosen(9, 3, 20), - Event::ValidatorChosen(9, 4, 20), - Event::ValidatorChosen(9, 5, 10), - Event::NewSession(40, 9, 4, 90), - Event::Withdrawn(6, 20), - Event::NominatorLeft(6, 20), - Event::Withdrawn(7, 10), - Event::NominatorLeft(7, 10), - Event::Withdrawn(10, 10), - Event::NominatorLeft(10, 10), - ]; - - expected.append(&mut new3); - assert_eq!(events(), expected); - - assert!(!NodleStaking::is_nominator(&6)); - assert!(!NodleStaking::is_nominator(&7)); - assert!(!NodleStaking::is_nominator(&10)); - - assert_eq!(mock::balances(&1), (100, 0)); - assert_eq!(mock::balances(&6), (100, 0)); - assert_eq!(mock::balances(&7), (100, 0)); - assert_eq!(mock::balances(&10), (100, 0)); - - assert_eq!(mock::balances(&2), (100, 20)); - assert_eq!(mock::balances(&8), (100, 10)); - assert_eq!(mock::balances(&9), (100, 10)); - - assert_eq!(NodleStaking::total(), 90); - }); -} - -#[test] -fn revoke_nomination_or_leave_nominators() { - ExtBuilder::default() - .with_balances(vec![ - (1, 100), - (2, 100), - (3, 100), - (4, 100), - (5, 100), - (6, 100), - (7, 100), - (8, 100), - (9, 100), - (10, 100), - ]) - .with_validators(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)]) - .with_nominators(vec![(6, 1, 10), (7, 1, 10), (8, 2, 10), (9, 2, 10), (10, 1, 10)]) - .tst_staking_build() - .execute_with(|| { - mock::start_active_session(4); - - let mut expected = vec![ - Event::ValidatorChosen(2, 1, 50), - Event::ValidatorChosen(2, 2, 40), - Event::ValidatorChosen(2, 3, 20), - Event::ValidatorChosen(2, 4, 20), - Event::ValidatorChosen(2, 5, 10), - Event::NewSession(5, 2, 5, 140), - Event::ValidatorChosen(3, 1, 50), - Event::ValidatorChosen(3, 2, 40), - Event::ValidatorChosen(3, 3, 20), - Event::ValidatorChosen(3, 4, 20), - Event::ValidatorChosen(3, 5, 10), - Event::NewSession(10, 3, 5, 140), - Event::ValidatorChosen(4, 1, 50), - Event::ValidatorChosen(4, 2, 40), - Event::ValidatorChosen(4, 3, 20), - Event::ValidatorChosen(4, 4, 20), - Event::ValidatorChosen(4, 5, 10), - Event::NewSession(15, 4, 5, 140), - Event::ValidatorChosen(5, 1, 50), - Event::ValidatorChosen(5, 2, 40), - Event::ValidatorChosen(5, 3, 20), - Event::ValidatorChosen(5, 4, 20), - Event::ValidatorChosen(5, 5, 10), - Event::NewSession(20, 5, 5, 140), - ]; - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&1), (100, 20)); - assert_eq!(Balances::total_balance(&1), 100); - - assert_eq!(mock::balances(&2), (100, 20)); - assert_eq!(Balances::total_balance(&2), 100); - - assert_eq!(mock::balances(&3), (100, 20)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 20)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&5), (100, 10)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 10)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(mock::balances(&7), (100, 10)); - assert_eq!(Balances::total_balance(&7), 100); - - assert_eq!(mock::balances(&8), (100, 10)); - assert_eq!(Balances::total_balance(&8), 100); - - assert_eq!(mock::balances(&9), (100, 10)); - assert_eq!(Balances::total_balance(&9), 100); - - assert_eq!(mock::balances(&10), (100, 10)); - assert_eq!(Balances::total_balance(&10), 100); - - assert_eq!(NodleStaking::total(), 140); - - assert_noop!( - NodleStaking::nominator_denominate(Origin::signed(1), 2), - Error::::NominatorDNE, - ); - assert_noop!( - NodleStaking::nominator_denominate(Origin::signed(6), 2), - Error::::NominationDNE, - ); - assert_noop!( - NodleStaking::nominator_denominate_all(Origin::signed(1)), - Error::::NominatorDNE, - ); - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(6), 2, 3, false)); - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(6), 3, 3, false)); - assert_ok!(NodleStaking::nominator_denominate(Origin::signed(6), 1)); - - let mut new1 = vec![ - Event::Nomination(6, 3, 2, 43), - Event::Nomination(6, 3, 3, 23), - Event::NominatorLeftValidator(6, 1, 10, 40), - ]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - mock::start_active_session(6); - - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(6))); - - let mut new2 = vec![ - Event::ValidatorChosen(6, 1, 40), - Event::ValidatorChosen(6, 2, 43), - Event::ValidatorChosen(6, 3, 23), - Event::ValidatorChosen(6, 4, 20), - Event::ValidatorChosen(6, 5, 10), - Event::NewSession(25, 6, 5, 136), - Event::ValidatorChosen(7, 1, 40), - Event::ValidatorChosen(7, 2, 43), - Event::ValidatorChosen(7, 3, 23), - Event::ValidatorChosen(7, 4, 20), - Event::ValidatorChosen(7, 5, 10), - Event::NewSession(30, 7, 5, 136), - Event::Withdrawn(6, 10), - ]; - - expected.append(&mut new2); - assert_eq!(events(), expected); - - assert_noop!( - NodleStaking::nominator_denominate(Origin::signed(6), 2), - Error::::NominatorBondBelowMin, - ); - assert_noop!( - NodleStaking::nominator_denominate(Origin::signed(6), 3), - Error::::NominatorBondBelowMin, - ); - // can revoke both remaining by calling leave nominators - assert_ok!(NodleStaking::nominator_denominate_all(Origin::signed(6))); - // this leads to 8 leaving set of nominators - assert_ok!(NodleStaking::nominator_denominate(Origin::signed(8), 2)); - assert_ok!(NodleStaking::nominator_denominate_all(Origin::signed(8))); - - let mut new3 = vec![ - Event::NominatorLeftValidator(6, 2, 3, 40), - Event::NominatorLeftValidator(6, 3, 3, 20), - Event::NominatorLeftValidator(8, 2, 10, 30), - ]; - - expected.append(&mut new3); - assert_eq!(events(), expected); - - mock::start_active_session(8); - - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(6))); - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(8))); - - let mut new4 = vec![ - Event::ValidatorChosen(8, 1, 40), - Event::ValidatorChosen(8, 2, 30), - Event::ValidatorChosen(8, 3, 20), - Event::ValidatorChosen(8, 4, 20), - Event::ValidatorChosen(8, 5, 10), - Event::NewSession(35, 8, 5, 120), - Event::ValidatorChosen(9, 1, 40), - Event::ValidatorChosen(9, 2, 30), - Event::ValidatorChosen(9, 3, 20), - Event::ValidatorChosen(9, 4, 20), - Event::ValidatorChosen(9, 5, 10), - Event::NewSession(40, 9, 5, 120), - Event::Withdrawn(6, 6), - Event::NominatorLeft(6, 6), - Event::Withdrawn(8, 10), - Event::NominatorLeft(8, 10), - ]; - - expected.append(&mut new4); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&6), (100, 0)); - assert_eq!(mock::balances(&8), (100, 0)); - - assert_eq!(NodleStaking::total(), 120); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - // panic!("S-1"); - }); -} - -#[test] -fn payouts_follow_nomination_changes() { - ExtBuilder::default() - .with_balances(vec![ - (1, 100), - (2, 100), - (3, 100), - (4, 100), - (5, 100), - (6, 100), - (7, 100), - (8, 100), - (9, 100), - (10, 100), - ]) - .with_validators(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)]) - .with_nominators(vec![(6, 1, 10), (7, 1, 10), (8, 2, 10), (9, 2, 10), (10, 1, 10)]) - .tst_staking_build() - .execute_with(|| { - mock::start_active_session(4); - - let mut expected = vec![ - Event::ValidatorChosen(2, 1, 50), - Event::ValidatorChosen(2, 2, 40), - Event::ValidatorChosen(2, 3, 20), - Event::ValidatorChosen(2, 4, 20), - Event::ValidatorChosen(2, 5, 10), - Event::NewSession(5, 2, 5, 140), - Event::ValidatorChosen(3, 1, 50), - Event::ValidatorChosen(3, 2, 40), - Event::ValidatorChosen(3, 3, 20), - Event::ValidatorChosen(3, 4, 20), - Event::ValidatorChosen(3, 5, 10), - Event::NewSession(10, 3, 5, 140), - Event::ValidatorChosen(4, 1, 50), - Event::ValidatorChosen(4, 2, 40), - Event::ValidatorChosen(4, 3, 20), - Event::ValidatorChosen(4, 4, 20), - Event::ValidatorChosen(4, 5, 10), - Event::NewSession(15, 4, 5, 140), - Event::ValidatorChosen(5, 1, 50), - Event::ValidatorChosen(5, 2, 40), - Event::ValidatorChosen(5, 3, 20), - Event::ValidatorChosen(5, 4, 20), - Event::ValidatorChosen(5, 5, 10), - Event::NewSession(20, 5, 5, 140), - ]; - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&1), (100, 20)); - assert_eq!(Balances::total_balance(&1), 100); - - assert_eq!(mock::balances(&2), (100, 20)); - assert_eq!(Balances::total_balance(&2), 100); - - assert_eq!(mock::balances(&3), (100, 20)); - assert_eq!(Balances::total_balance(&3), 100); - - assert_eq!(mock::balances(&4), (100, 20)); - assert_eq!(Balances::total_balance(&4), 100); - - assert_eq!(mock::balances(&5), (100, 10)); - assert_eq!(Balances::total_balance(&5), 100); - - assert_eq!(mock::balances(&6), (100, 10)); - assert_eq!(Balances::total_balance(&6), 100); - - assert_eq!(mock::balances(&7), (100, 10)); - assert_eq!(Balances::total_balance(&7), 100); - - assert_eq!(mock::balances(&8), (100, 10)); - assert_eq!(Balances::total_balance(&8), 100); - - assert_eq!(mock::balances(&9), (100, 10)); - assert_eq!(Balances::total_balance(&9), 100); - - assert_eq!(mock::balances(&10), (100, 10)); - assert_eq!(Balances::total_balance(&10), 100); - - assert_eq!(NodleStaking::total(), 140); - - set_author(4, 1, 100); - mock::mint_rewards(1_000_000); - mock::start_active_session(5); - - let mut new1 = vec![ - Event::StakeReward(1, 520000), - Event::StakeReward(6, 160000), - Event::StakeReward(7, 160000), - Event::StakeReward(10, 160000), - Event::ValidatorChosen(6, 1, 50), - Event::ValidatorChosen(6, 2, 40), - Event::ValidatorChosen(6, 3, 20), - Event::ValidatorChosen(6, 4, 20), - Event::ValidatorChosen(6, 5, 10), - Event::NewSession(25, 6, 5, 140), - ]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - // ~ set block author as 1 for all blocks this round - set_author(5, 1, 100); - mock::mint_rewards(1_000_000); - - // 1. ensure nominators are paid for 2 rounds after they leave - assert_noop!( - NodleStaking::nominator_denominate_all(Origin::signed(66)), - Error::::NominatorDNE - ); - - assert_ok!(NodleStaking::nominator_denominate_all(Origin::signed(6))); - - mock::start_active_session(6); - - let mut new2 = vec![ - Event::NominatorLeftValidator(6, 1, 10, 40), - Event::StakeReward(1, 520000), - Event::StakeReward(6, 160000), - Event::StakeReward(7, 160000), - Event::StakeReward(10, 160000), - Event::ValidatorChosen(7, 1, 40), - Event::ValidatorChosen(7, 2, 40), - Event::ValidatorChosen(7, 3, 20), - Event::ValidatorChosen(7, 4, 20), - Event::ValidatorChosen(7, 5, 10), - Event::NewSession(30, 7, 5, 130), - ]; - - expected.append(&mut new2); - assert_eq!(events(), expected); - - set_author(6, 1, 100); - mock::mint_rewards(1_000_000); - mock::start_active_session(7); - - let mut new3 = vec![ - Event::StakeReward(1, 520000), - Event::StakeReward(6, 160000), - Event::StakeReward(7, 160000), - Event::StakeReward(10, 160000), - Event::ValidatorChosen(8, 1, 40), - Event::ValidatorChosen(8, 2, 40), - Event::ValidatorChosen(8, 3, 20), - Event::ValidatorChosen(8, 4, 20), - Event::ValidatorChosen(8, 5, 10), - Event::NewSession(35, 8, 5, 130), - ]; - expected.append(&mut new3); - assert_eq!(events(), expected); - - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(8), 1, 10, false)); - - mock::start_active_session(8); - - let mut new4 = vec![ - Event::Nomination(8, 10, 1, 50), - Event::ValidatorChosen(9, 1, 50), - Event::ValidatorChosen(9, 2, 40), - Event::ValidatorChosen(9, 3, 20), - Event::ValidatorChosen(9, 4, 20), - Event::ValidatorChosen(9, 5, 10), - Event::NewSession(40, 9, 5, 140), - ]; - expected.append(&mut new4); - assert_eq!(events(), expected); - - set_author(8, 1, 100); - mock::mint_rewards(1_000_000); - - mock::start_active_session(10); - - let mut new5 = vec![ - Event::StakeReward(1, 600000), - Event::StakeReward(7, 200000), - Event::StakeReward(10, 200000), - Event::ValidatorChosen(10, 1, 50), - Event::ValidatorChosen(10, 2, 40), - Event::ValidatorChosen(10, 3, 20), - Event::ValidatorChosen(10, 4, 20), - Event::ValidatorChosen(10, 5, 10), - Event::NewSession(45, 10, 5, 140), - Event::ValidatorChosen(11, 1, 50), - Event::ValidatorChosen(11, 2, 40), - Event::ValidatorChosen(11, 3, 20), - Event::ValidatorChosen(11, 4, 20), - Event::ValidatorChosen(11, 5, 10), - Event::NewSession(50, 11, 5, 140), - ]; - expected.append(&mut new5); - assert_eq!(events(), expected); - - set_author(10, 1, 100); - mock::mint_rewards(1_000_000); - - mock::start_active_session(11); - - let mut new6 = vec![ - Event::StakeReward(1, 520000), - Event::StakeReward(7, 160000), - Event::StakeReward(8, 160000), - Event::StakeReward(10, 160000), - Event::ValidatorChosen(12, 1, 50), - Event::ValidatorChosen(12, 2, 40), - Event::ValidatorChosen(12, 3, 20), - Event::ValidatorChosen(12, 4, 20), - Event::ValidatorChosen(12, 5, 10), - Event::NewSession(55, 12, 5, 140), - ]; - expected.append(&mut new6); - assert_eq!(events(), expected); - - assert_ok!(NodleStaking::withdraw_unbonded(Origin::signed(6))); - - let mut new7 = vec![Event::Withdrawn(6, 10), Event::NominatorLeft(6, 10)]; - expected.append(&mut new7); - - assert_eq!(events(), expected); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(6))); - - let mut new8 = vec![Event::Rewarded(6, 480000)]; - expected.append(&mut new8); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&6), (480100, 0)); - assert_eq!(NodleStaking::total(), 140); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - }); -} - -#[test] -fn set_invulnerables_works() { - ExtBuilder::default().build_and_execute(|| { - let new_set1 = vec![1, 2]; - - assert_ok!(NodleStaking::set_invulnerables(Origin::root(), new_set1.clone())); - assert_eq!(NodleStaking::invulnerables(), new_set1); - - let mut expected = vec![Event::NewInvulnerables([1, 2].to_vec())]; - assert_eq!(events(), expected); - - let new_set2 = vec![3, 4]; - - assert_ok!(NodleStaking::set_invulnerables( - Origin::signed(CancelOrigin::get()), - new_set2.clone() - )); - assert_eq!(NodleStaking::invulnerables(), new_set2); - - let mut new1 = vec![Event::NewInvulnerables([3, 4].to_vec())]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - // cannot set with non-root. - assert_noop!(NodleStaking::set_invulnerables(Origin::signed(1), new_set2), BadOrigin); - }); -} - -#[test] -fn set_total_validator_per_round_works() { - ExtBuilder::default().build_and_execute(|| { - let old_total_selected = NodleStaking::total_selected(); - let new_total_selected = old_total_selected * 4u32; - - assert_noop!( - NodleStaking::set_total_validator_per_round(Origin::signed(1), new_total_selected), - BadOrigin - ); - - assert_ok!(NodleStaking::set_total_validator_per_round( - Origin::signed(CancelOrigin::get()), - new_total_selected - )); - - let mut expected = vec![Event::TotalSelectedSet(old_total_selected, new_total_selected)]; - assert_eq!(events(), expected); - - assert_eq!(NodleStaking::total_selected(), new_total_selected); - - let new_total_selected = old_total_selected * 2u32; - - assert_ok!(NodleStaking::set_total_validator_per_round( - Origin::root(), - new_total_selected - )); - - let mut new1 = vec![Event::TotalSelectedSet(old_total_selected * 4u32, new_total_selected)]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(NodleStaking::total_selected(), new_total_selected); - }); -} - -#[test] -fn set_staking_limits_works() { - ExtBuilder::default().build_and_execute(|| { - let old_max_validators = NodleStaking::staking_max_validators(); - let old_min_stake = NodleStaking::staking_min_stake_session_selection(); - let old_min_validator_bond = NodleStaking::staking_min_validator_bond(); - let old_min_nomination_total_bond = NodleStaking::staking_min_nominator_total_bond(); - let old_min_nomination_chill_threshold = NodleStaking::staking_min_nomination_chill_threshold(); - - let new_max_validators = 100; - let new_min_stake = old_min_stake.saturating_mul(4u32.into()); - let new_min_validator_bond = old_min_validator_bond.saturating_mul(4u32.into()); - let new_min_nomination_total_bond = old_min_nomination_total_bond.saturating_mul(4u32.into()); - let new_min_nomination_chill_threshold = old_min_nomination_chill_threshold.saturating_mul(4u32.into()); - - assert_noop!( - NodleStaking::set_staking_limits( - Origin::signed(1), - new_max_validators, - new_min_stake, - new_min_validator_bond, - new_min_nomination_total_bond, - new_min_nomination_chill_threshold, - ), - BadOrigin - ); - - assert_ok!(NodleStaking::set_staking_limits( - Origin::signed(CancelOrigin::get()), - new_max_validators, - new_min_stake, - new_min_validator_bond, - new_min_nomination_total_bond, - new_min_nomination_chill_threshold, - )); - - let mut expected = vec![Event::NewStakingLimits( - old_max_validators, - new_max_validators, - old_min_stake, - new_min_stake, - old_min_validator_bond, - new_min_validator_bond, - old_min_nomination_total_bond, - new_min_nomination_total_bond, - old_min_nomination_chill_threshold, - new_min_nomination_chill_threshold, - )]; - assert_eq!(events(), expected); - - assert_eq!(NodleStaking::staking_max_validators(), new_max_validators); - assert_eq!(NodleStaking::staking_min_stake_session_selection(), new_min_stake); - assert_eq!(NodleStaking::staking_min_validator_bond(), new_min_validator_bond); - assert_eq!( - NodleStaking::staking_min_nominator_total_bond(), - new_min_nomination_total_bond - ); - assert_eq!( - NodleStaking::staking_min_nomination_chill_threshold(), - new_min_nomination_chill_threshold - ); - - let new2_max_validators = 75; - let new2_min_stake = old_min_stake.saturating_mul(2u32.into()); - let new2_min_validator_bond = old_min_validator_bond.saturating_mul(2u32.into()); - let new2_min_nomination_total_bond = old_min_nomination_total_bond.saturating_mul(2u32.into()); - let new2_min_nomination_chill_threshold = old_min_nomination_chill_threshold.saturating_mul(2u32.into()); - - assert_ok!(NodleStaking::set_staking_limits( - Origin::root(), - new2_max_validators, - new2_min_stake, - new2_min_validator_bond, - new2_min_nomination_total_bond, - new2_min_nomination_chill_threshold, - )); - - let mut new1 = vec![Event::NewStakingLimits( - new_max_validators, - new2_max_validators, - new_min_stake, - new2_min_stake, - new_min_validator_bond, - new2_min_validator_bond, - new_min_nomination_total_bond, - new2_min_nomination_total_bond, - new_min_nomination_chill_threshold, - new2_min_nomination_chill_threshold, - )]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(NodleStaking::staking_max_validators(), new2_max_validators); - assert_eq!(NodleStaking::staking_min_stake_session_selection(), new2_min_stake); - assert_eq!(NodleStaking::staking_min_validator_bond(), new2_min_validator_bond); - assert_eq!( - NodleStaking::staking_min_nominator_total_bond(), - new2_min_nomination_total_bond - ); - assert_eq!( - NodleStaking::staking_min_nomination_chill_threshold(), - new2_min_nomination_chill_threshold - ); - }); -} - -#[test] -fn payout_creates_controller() { - ExtBuilder::default().build_and_execute(|| { - let balance = 1000; - // Create a validator: - bond_validator(10, balance); - - assert_eq!( - last_event(), - MetaEvent::NodleStaking(Event::JoinedValidatorPool(10, balance, 4500)), - ); - - // Create a nominator - bond_nominator(1337, 100, 10); - - assert_eq!( - last_event(), - MetaEvent::NodleStaking(Event::Nomination(1337, 100, 10, 1100,),), - ); - - mock::mint_rewards(1_000_000); - - mock::start_active_session(1); - - mock::mint_rewards(1_000_000); - // NodleStaking::reward_by_ids(vec![(11, 1)]); - - mock::start_active_session(2); - - // tst_tst_log!(trace, - // "Actv Sess 2 event {:#?}", - // mock::events() - // ); - - // assert_ok!(NodleStaking::payout_stakers(Origin::signed(1337), 11, 1)); - - // // Controller is created - // assert!(Balances::free_balance(1337) > 0); - }) -} - -#[test] -fn reward_from_authorship_event_handler_works() { - ExtBuilder::default().num_validators(4).build_and_execute(|| { - use pallet_authorship::EventHandler; - assert_eq!(>::author(), Some(11)); - NodleStaking::note_author(11); - NodleStaking::note_uncle(21, 1); - // Rewarding the same two times works. - NodleStaking::note_uncle(11, 1); - // Not mandatory but must be coherent with rewards - assert_eq!(Session::validators(), vec![11, 21, 41]); - - // 11 is rewarded as a block producer and uncle reference and uncle producer - assert_eq!(NodleStaking::awarded_pts(NodleStaking::active_session(), 11), 25); - - // 21 is rewarded as an uncle producer - assert_eq!(NodleStaking::awarded_pts(NodleStaking::active_session(), 21), 1); - - // Total rewarded points - assert_eq!(NodleStaking::points(NodleStaking::active_session()), 26); - }) -} - -#[test] -fn add_reward_points_fns_works() { - ExtBuilder::default().num_validators(4).build_and_execute(|| { - // Not mandatory but must be coherent with rewards - assert_eq!(Session::validators(), vec![11, 21, 41]); - NodleStaking::reward_by_ids(vec![(21, 1), (11, 1), (11, 1)]); - NodleStaking::reward_by_ids(vec![(21, 1), (11, 1), (11, 1), (41, 2)]); - - assert_eq!(NodleStaking::awarded_pts(NodleStaking::active_session(), 11), 4); - - assert_eq!(NodleStaking::awarded_pts(NodleStaking::active_session(), 21), 2); - - assert_eq!(NodleStaking::awarded_pts(NodleStaking::active_session(), 41), 2); - - assert_eq!(NodleStaking::points(NodleStaking::active_session()), 8); - }) -} - -#[test] -fn reward_validator_slashing_validator_does_not_overflow() { - ExtBuilder::default() - .num_validators(4) - .has_stakers(true) - .build_and_execute(|| { - let stake = u64::max_value() as Balance * 2; - let reward_slash = u64::max_value() as Balance * 2; - - // --- Session 2: - start_session(2); - - // Assert multiplication overflows in balance arithmetic. - assert!(stake.checked_mul(reward_slash).is_none()); - - // Set staker - let _ = Balances::make_free_balance_be(&81, stake); - - bond_validator(81, stake); - - assert_ok!(Session::set_keys( - Origin::signed(81), - UintAuthorityId(81).into(), - vec![] - )); - - // --- Session 3: - start_session(3); - - assert_eq!(mock::validators_in_pool(), vec![11, 21, 41, 81]); - assert_eq!(mock::selected_validators(), vec![11, 21, 41, 81]); - - // Inject reward - Points::::insert(3, 1); - AwardedPts::::insert(3, 81, 1); - SessionAccumulatedBalance::::insert(3, stake); - assert_eq!(NodleStaking::total(), 36893488147419106730); - - // --- Session 4: - // Trigger payout for Session 3 - start_session(4); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - Event::ValidatorChosen(3, 11, 1500), - Event::ValidatorChosen(3, 21, 1000), - Event::ValidatorChosen(3, 41, 1000), - Event::NewSession(10, 3, 3, 3500), - Event::JoinedValidatorPool(81, 36893488147419103230, 36893488147419106730), - Event::ValidatorChosen(4, 11, 1500), - Event::ValidatorChosen(4, 21, 1000), - Event::ValidatorChosen(4, 41, 1000), - Event::ValidatorChosen(4, 81, 36893488147419103230), - Event::NewSession(15, 4, 4, 36893488147419106730), - Event::StakeReward(81, 36893488147419103230), - Event::ValidatorChosen(5, 11, 1500), - Event::ValidatorChosen(5, 21, 1000), - Event::ValidatorChosen(5, 41, 1000), - Event::ValidatorChosen(5, 81, 36893488147419103230), - Event::NewSession(20, 5, 4, 36893488147419106730), - ]; - assert_eq!(events(), expected); - - assert_ok!(NodleStaking::withdraw_staking_rewards(Origin::signed(81))); - - let mut new1 = vec![Event::Rewarded(81, 36893488147419103230)]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - // Payout made for Acc-81 - assert_eq!(Balances::total_balance(&81), (stake * 2)); - - // Inject reward - Points::::insert(4, 1); - AwardedPts::::insert(4, 81, 1); - SessionAccumulatedBalance::::insert(4, stake); - - // Add Nominators to Validator 81 - let _ = Balances::make_free_balance_be(&201, stake); - let _ = Balances::make_free_balance_be(&301, stake); - - bond_nominator(201, stake - 1, 81); - bond_nominator(301, stake - 1, 81); - - // --- Session 5: - start_session(5); - - // Ensure nominators are updated to - // AtStake Validator snapshot - assert_eq!( - NodleStaking::at_stake(6, 81), - ValidatorSnapshot { - bond: 36893488147419103230, - nominators: vec![ - Bond { - owner: 201, - amount: 36893488147419103229 - }, - Bond { - owner: 301, - amount: 36893488147419103229 - }, - ], - total: 110680464442257309688, - } - ); - - log::trace!( - "reward_validator_slashing_validator_does_not_overflow:[{:#?}] - Bonded Sess - {:#?}", - line!(), - NodleStaking::bonded_sessions() - ); - - log::trace!( - "reward_validator_slashing_validator_does_not_overflow:[{:#?}] - AtStake - {:#?}", - line!(), - NodleStaking::at_stake(4, 81) - ); - - // Check slashing - on_offence_in_session( - &[OffenceDetails { - offender: (81, NodleStaking::at_stake(4, 81)), - reporters: vec![], - }], - &[Perbill::from_percent(50)], - 4, - DisableStrategy::Always, - ); - - // assert_eq!(Balances::total_balance(&11), stake - 1); - // assert_eq!(Balances::total_balance(&2), 1); - }); -} - -#[test] -fn nominators_also_get_slashed_pro_rata() { - ExtBuilder::default().num_validators(4).build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(events(), expected); - - let slash_percent = Perbill::from_percent(5); - let initial_exposure = NodleStaking::at_stake(NodleStaking::active_session(), 11); - - // 101 is a nominator for 11 - assert_eq!(initial_exposure.nominators.first().unwrap().owner, 101); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(NodleStaking::total(), 3500); - - // staked values; - let nominator_stake = NodleStaking::nominator_state(101).unwrap().total; - let validator_stake = NodleStaking::validator_state(11).unwrap().bond; - let exposed_stake = initial_exposure.total; - let exposed_validator = initial_exposure.bond; - let exposed_nominator = initial_exposure.nominators.first().unwrap().amount; - - mock::on_offence_now( - &[OffenceDetails { - offender: (11, initial_exposure), - reporters: vec![], - }], - &[slash_percent], - DisableStrategy::Always, - ); - - // Ensure both Validator & Nominator are slashed. - let mut new1 = vec![Event::Slash(11, 50), Event::Slash(101, 25)]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - // both stakes must have been decreased. - assert!(NodleStaking::nominator_state(101).unwrap().total < nominator_stake); - assert!(NodleStaking::validator_state(11).unwrap().bond < validator_stake); - assert_eq!(NodleStaking::total(), 3425); - - let slash_amount = slash_percent * exposed_stake; - let validator_share = Perbill::from_rational(exposed_validator, exposed_stake) * slash_amount; - let nominator_share = Perbill::from_rational(exposed_nominator, exposed_stake) * slash_amount; - - // both slash amounts need to be positive for the test to make sense. - assert!(validator_share > 0); - assert!(nominator_share > 0); - - // both stakes must have been decreased pro-rata. - assert_eq!( - NodleStaking::nominator_state(101).unwrap().total, - nominator_stake - nominator_share, - ); - assert_eq!( - NodleStaking::validator_state(11).unwrap().bond, - validator_stake - validator_share, - ); - assert_eq!(mock::balances(&101), (1975, nominator_stake - nominator_share)); - assert_eq!(mock::balances(&11), (1950, validator_stake - validator_share),); - - // Because slashing happened. - assert!(is_disabled(11)); - }); -} - -#[test] -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().num_validators(4).build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(NodleStaking::total(), 3500); - - let validator_bond_value = mock::balances(&11).1; - - assert_eq!(NodleStaking::at_stake(NodleStaking::active_session(), 11).bond, 1000); - - // Handle an offence with a historical exposure. - on_offence_now( - &[OffenceDetails { - offender: ( - 11, - ValidatorSnapshot { - total: 500, - bond: 500, - nominators: vec![], - }, - ), - reporters: vec![], - }], - &[Perbill::from_percent(50)], - DisableStrategy::Always, - ); - - let mut new2 = vec![Event::Slash(11, 250)]; - expected.append(&mut new2); - assert_eq!(events(), expected); - - // The validator controller account should be slashed for 250 (50% of 500). - assert_eq!(mock::balances(&11), (1750, validator_bond_value - 250)); - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(NodleStaking::total(), 3250); - }); -} - -#[test] -fn slash_in_old_span_does_not_deselect() { - ExtBuilder::default() - .bonded_duration(10) - .num_validators(4) - .build_and_execute(|| { - mock::start_active_session(5); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - Event::ValidatorChosen(3, 11, 1500), - Event::ValidatorChosen(3, 21, 1000), - Event::ValidatorChosen(3, 41, 1000), - Event::NewSession(10, 3, 3, 3500), - Event::ValidatorChosen(4, 11, 1500), - Event::ValidatorChosen(4, 21, 1000), - Event::ValidatorChosen(4, 41, 1000), - Event::NewSession(15, 4, 3, 3500), - Event::ValidatorChosen(5, 11, 1500), - Event::ValidatorChosen(5, 21, 1000), - Event::ValidatorChosen(5, 41, 1000), - Event::NewSession(20, 5, 3, 3500), - Event::ValidatorChosen(6, 11, 1500), - Event::ValidatorChosen(6, 21, 1000), - Event::ValidatorChosen(6, 41, 1000), - Event::NewSession(25, 6, 3, 3500), - ]; - assert_eq!(events(), expected); - - mock::set_author(5, 11, 20); - mock::set_author(5, 21, 40); - mock::set_author(5, 41, 40); - - assert!(>::contains_key(5, 11)); - assert!(>::contains_key(5, 21)); - assert!(>::contains_key(5, 41)); - - assert!(>::contains_key(11)); - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - - assert!(Session::validators().contains(&11)); - assert_eq!(NodleStaking::total(), 3500); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(20)], - DisableStrategy::Always, - ); - - assert_eq!(NodleStaking::validator_state(11).unwrap().state, ValidatorStatus::Idle); - - let mut new1 = vec![Event::Slash(11, 200), Event::Slash(101, 100)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - assert_eq!(NodleStaking::total(), 3200); - - mock::start_active_session(8); - - let mut new2 = vec![ - Event::ValidatorChosen(7, 21, 1000), - Event::ValidatorChosen(7, 41, 1000), - Event::NewSession(30, 7, 2, 2000), - Event::ValidatorChosen(8, 21, 1000), - Event::ValidatorChosen(8, 41, 1000), - Event::NewSession(35, 8, 2, 2000), - Event::ValidatorChosen(9, 21, 1000), - Event::ValidatorChosen(9, 41, 1000), - Event::NewSession(40, 9, 2, 2000), - ]; - - expected.append(&mut new2); - assert_eq!(events(), expected); - - assert!(>::contains_key(5, 11)); - assert!(>::contains_key(5, 21)); - assert!(>::contains_key(5, 41)); - - assert_eq!(NodleStaking::total(), 3200); - - assert_ok!(NodleStaking::validator_bond_more(Origin::signed(11), 10)); - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active - ); - assert_eq!( - mock::last_event(), - MetaEvent::NodleStaking(Event::ValidatorBondedMore(11, 800, 810)) - ); - - mock::start_active_session(11); - - let mut new3 = vec![ - Event::ValidatorBondedMore(11, 800, 810), - Event::ValidatorChosen(10, 11, 1210), - Event::ValidatorChosen(10, 21, 1000), - Event::ValidatorChosen(10, 41, 1000), - Event::NewSession(45, 10, 3, 3210), - Event::ValidatorChosen(11, 11, 1210), - Event::ValidatorChosen(11, 21, 1000), - Event::ValidatorChosen(11, 41, 1000), - Event::NewSession(50, 11, 3, 3210), - Event::ValidatorChosen(12, 11, 1210), - Event::ValidatorChosen(12, 21, 1000), - Event::ValidatorChosen(12, 41, 1000), - Event::NewSession(55, 12, 3, 3210), - ]; - expected.append(&mut new3); - assert_eq!(mock::events(), expected); - assert_eq!(NodleStaking::total(), 3210); - - mock::start_active_session(14); - - let bonded_sess_state1 = vec![4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; - assert_eq!(NodleStaking::bonded_sessions(), bonded_sess_state1); - - let mut new4 = vec![ - Event::ValidatorChosen(13, 11, 1210), - Event::ValidatorChosen(13, 21, 1000), - Event::ValidatorChosen(13, 41, 1000), - Event::NewSession(60, 13, 3, 3210), - Event::ValidatorChosen(14, 11, 1210), - Event::ValidatorChosen(14, 21, 1000), - Event::ValidatorChosen(14, 41, 1000), - Event::NewSession(65, 14, 3, 3210), - Event::ValidatorChosen(15, 11, 1210), - Event::ValidatorChosen(15, 21, 1000), - Event::ValidatorChosen(15, 41, 1000), - Event::NewSession(70, 15, 3, 3210), - ]; - expected.append(&mut new4); - assert_eq!(mock::events(), expected); - assert_eq!(NodleStaking::total(), 3210); - - on_offence_in_session( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(5, 11)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - 5, - DisableStrategy::Always, - ); - - // Ensure no new events - // Since slash 10% is less-than prev-slash 20%. - assert_eq!(events(), expected); - - on_offence_in_session( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(5, 11)), - reporters: vec![], - }], - &[Perbill::from_percent(30)], - 5, - DisableStrategy::Always, - ); - - // Ensure Validator 11 & nominator are slashed - let mut new5 = vec![Event::Slash(11, 100), Event::Slash(101, 50)]; - expected.append(&mut new5); - assert_eq!(events(), expected); - assert_eq!(NodleStaking::total(), 3060); - - // Ensure Validator 11 is not deactivated - // Since offence incident is reported on old slash span. - assert_eq!( - NodleStaking::validator_state(11).unwrap().state, - ValidatorStatus::Active, - ); - - mock::start_active_session(16); - - assert!(!>::contains_key(5, 11)); - assert!(!>::contains_key(5, 21)); - assert!(!>::contains_key(5, 41)); - }); -} - -#[test] -fn reporters_receive_their_slice() { - // This test verifies that the reporters of the offence receive their slice from the slashed - // amount. - ExtBuilder::default().num_validators(4).build_and_execute(|| { - // The reporters' reward is calculated from the total exposure. - let initial_balance = 1500; - - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(events(), expected); - - assert_eq!( - NodleStaking::at_stake(NodleStaking::active_session(), 11).total, - initial_balance - ); - - assert_eq!(NodleStaking::total(), 3500); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![1, 2], - }], - &[Perbill::from_percent(50)], - DisableStrategy::Always, - ); - - let mut new2 = vec![ - Event::Slash(11, 500), - Event::Slash(101, 250), - Event::PayReporterReward(1, 18), - Event::PayReporterReward(2, 18), - ]; - expected.append(&mut new2); - assert_eq!(events(), expected); - assert_eq!(NodleStaking::total(), 2750); - - // F1 * (reward_proportion * slash - 0) - // 50% * (10% * initial_balance / 2) - let reward = (initial_balance / 20) / 2; - let reward_each = reward / 2; // split into two pieces. - assert_eq!(Balances::free_balance(1), 10 + reward_each); - assert_eq!(Balances::free_balance(2), 20 + reward_each); - assert_eq!(NodleStaking::total(), 2750); - }); -} - -#[test] -fn subsequent_reports_in_same_span_pay_out_less() { - // This test verifies that the reporters of the offence receive their slice from the slashed - // amount, but less and less if they submit multiple reports in one span. - ExtBuilder::default().num_validators(4).build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(events(), expected); - - let initial_balance = NodleStaking::validator_state(11).unwrap().total; - - assert_eq!( - NodleStaking::at_stake(NodleStaking::active_session(), 11).total, - initial_balance - ); - assert_eq!(NodleStaking::total(), 3500); - - log::trace!( - "subsequent_reports_in_same_span_pay_out_less:[{:#?}]=> Acc-1[{:#?}]", - line!(), - Balances::free_balance(1) - ); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![1], - }], - &[Perbill::from_percent(20)], - DisableStrategy::Always, - ); - - let mut new1 = vec![ - Event::Slash(11, 200), - Event::Slash(101, 100), - Event::PayReporterReward(1, 15), - ]; - expected.append(&mut new1); - assert_eq!(events(), expected); - assert_eq!(NodleStaking::total(), 3200); - - // F1 * (reward_proportion * slash - 0) - // 50% * (10% * initial_balance * 20%) - let prior_slash = initial_balance / 5; - let reward = prior_slash / 20; - assert_eq!(Balances::free_balance(1), 10 + reward); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![1], - }], - &[Perbill::from_percent(25)], - DisableStrategy::Always, - ); - - let prior_payout = reward; - - // F1 * (reward_proportion * slash - prior_payout) - // 50% * (10% * (initial_balance / 4) - prior_payout) - // TODO :: Have to recheck the rounding error. - let curr_slash = initial_balance / 4; - let reward = (((curr_slash / 10) - prior_payout) / 2) - 1u128; - - let mut new2 = vec![ - Event::Slash(11, 50), - Event::Slash(101, 25), - Event::PayReporterReward(1, 10), - ]; - expected.append(&mut new2); - assert_eq!(events(), expected); - - assert_eq!(Balances::free_balance(1), 10 + prior_payout + reward); - assert_eq!(NodleStaking::total(), 3125); - }); -} - -#[test] -fn invulnerables_are_not_slashed() { - // For invulnerable validators no slashing is performed. - ExtBuilder::default() - .num_validators(4) - .invulnerables(vec![11]) - .build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(events(), expected); - - Balances::make_free_balance_be(&201, 1000); - - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(201), 21, 500, false,)); - - mock::start_active_session(3); - - let mut new1 = vec![ - Event::Nomination(201, 500, 21, 1500), - Event::ValidatorChosen(3, 11, 1500), - Event::ValidatorChosen(3, 21, 1500), - Event::ValidatorChosen(3, 41, 1000), - Event::NewSession(10, 3, 3, 4000), - Event::ValidatorChosen(4, 11, 1500), - Event::ValidatorChosen(4, 21, 1500), - Event::ValidatorChosen(4, 41, 1000), - Event::NewSession(15, 4, 3, 4000), - ]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(mock::balances(&21), (2000, 1000)); - assert_eq!(mock::balances(&201), (1000, 500)); - assert_eq!(NodleStaking::total(), 4000); - - let valid21_exposure = NodleStaking::at_stake(NodleStaking::active_session(), 21); - let validator21_initial_bond = valid21_exposure.bond; - let validator21_nominator_initial_bond: Vec<_> = - valid21_exposure.nominators.iter().map(|nom| nom.amount).collect(); - - on_offence_now( - &[ - OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }, - OffenceDetails { - offender: (21, NodleStaking::at_stake(NodleStaking::active_session(), 21)), - reporters: vec![], - }, - ], - &[Perbill::from_percent(50), Perbill::from_percent(20)], - DisableStrategy::Always, - ); - - // Ensure Validator-11 is not slashed - // Ensure Validator-21 & nominator-201 are slashed - let mut new2 = vec![Event::Slash(21, 200), Event::Slash(201, 100)]; - - expected.append(&mut new2); - assert_eq!(events(), expected); - - // The validator 11 hasn't been slashed, but 21 has been. - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(NodleStaking::total(), 3700); - // 2000 - (0.2 * initial_balance) - assert_eq!( - mock::balances(&21), - (1800, validator21_initial_bond - (2 * validator21_initial_bond / 10)) - ); - - // ensure that nominators were slashed as well. - for (initial_bond, nominator) in validator21_nominator_initial_bond - .into_iter() - .zip(valid21_exposure.nominators) - { - assert_eq!( - mock::balances(&nominator.owner), - (900, initial_bond - (2 * initial_bond / 10)), - ); - } - assert_eq!(NodleStaking::total(), 3700); - }); -} - -#[test] -fn dont_slash_if_fraction_is_zero() { - // Don't slash if the fraction is zero. - ExtBuilder::default().num_validators(4).build_and_execute(|| { - mock::start_active_session(1); - - let expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(NodleStaking::total(), 3500); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(0)], - DisableStrategy::Always, - ); - - // Ensure no slash or new events - assert_eq!(events(), expected); - - // The validator hasn't been slashed. The new era is not forced. - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(NodleStaking::total(), 3500); - }); -} - -#[test] -fn only_slash_for_max_in_session() { - // multiple slashes within one session are only applied if it is - // more than any previous slash in the same session. - ExtBuilder::default().num_validators(4).build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(mock::balances(&21), (2000, 1000)); - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(NodleStaking::total(), 3500); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(50)], - DisableStrategy::Always, - ); - - let mut new1 = vec![Event::Slash(11, 500), Event::Slash(101, 250)]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&11), (1500, 500)); - assert_eq!(mock::balances(&101), (1750, 250)); - assert_eq!(NodleStaking::total(), 2750); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(25)], - DisableStrategy::Always, - ); - - // slash fraction 25% is less than last value of 50% - // Ensure no events are fired - assert_eq!(events(), expected); - - // The validator has not been slashed additionally. - assert_eq!(mock::balances(&11), (1500, 500)); - assert_eq!(mock::balances(&101), (1750, 250)); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(60)], - DisableStrategy::Always, - ); - - // slash fraction 60% is more than last slash fraction of 50% - // Ensure Validator 11 & nominator 101 are slashed with diff. - let mut new2 = vec![Event::Slash(11, 100), Event::Slash(101, 50)]; - - expected.append(&mut new2); - assert_eq!(events(), expected); - - // The validator got slashed 10% more. - assert_eq!(mock::balances(&11), (1400, 400)); - assert_eq!(mock::balances(&101), (1700, 200)); - assert_eq!(NodleStaking::total(), 2600); - }) -} - -#[test] -fn garbage_collection_after_slashing() { - // ensures that `SlashingSpans` and `SpanSlash` of an account is removed after reaping. - ExtBuilder::default() - .num_validators(4) - .existential_deposit(2) - .build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 384000), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 256000), - Event::NewSession(5, 2, 3, 641000), - ]; - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&11), (512000, 256000)); - assert_eq!(mock::balances(&101), (512000, 128000)); - - let slash_percent = Perbill::from_percent(10); - let initial_exposure = NodleStaking::at_stake(NodleStaking::active_session(), 11); - - // 101 is a nominator for 11 - assert_eq!(initial_exposure.nominators.first().unwrap().owner, 101); - - assert_eq!(NodleStaking::total(), 641000); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[slash_percent], - DisableStrategy::Always, - ); - - let mut new1 = vec![Event::Slash(11, 25600), Event::Slash(101, 12800)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(balances(&11), (512000 - 25600, 256000 - (256000 / 10))); - assert_eq!(balances(&101), (512000 - 12800, 128000 - (128000 / 10))); - - assert_eq!(NodleStaking::total(), 602600); - - assert!(>::get(&11).is_some()); - - assert_eq!(>::get(&(11, 0)).amount_slashed(), &(256000 / 10)); - assert_eq!(>::get(&(101, 0)).amount_slashed(), &(128000 / 10)); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(100)], - DisableStrategy::Always, - ); - - let mut new2 = vec![ - Event::Slash(11, 230398), - Event::Slash(101, 115198), - Event::NominationBelowThreashold(101, 11, 2, 2, 0), - ]; - - expected.append(&mut new2); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&11), (256002, 2)); - assert_eq!(mock::balances(&101), (384002, 2)); - assert_eq!(NodleStaking::total(), 257004); - - let slashing_spans = >::get(&11).unwrap(); - assert_eq!(slashing_spans.iter().count(), 2); - - // TODO :: Validation of DB instance Clean-off pending - // // reap_stash respects num_slashing_spans so that weight is accurate - // assert_noop!(Staking::reap_stash(Origin::none(), 11, 0), - // Error::::IncorrectSlashingSpans); - // assert_ok!(Staking::reap_stash(Origin::none(), 11, 2)); - - // assert!(::SlashingSpans::get(&11).is_none()); - // assert_eq!(::SpanSlash::get(&(11, 0)).amount_slashed(), &0); - }) -} - -#[test] -fn garbage_collection_after_slashing_ed_1() { - // ensures that `SlashingSpans` and `SpanSlash` of an account is removed after reaping. - ExtBuilder::default().num_validators(4).build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(NodleStaking::total(), 3500); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - DisableStrategy::Always, - ); - - let mut new1 = vec![Event::Slash(11, 100), Event::Slash(101, 50)]; - - expected.append(&mut new1); - assert_eq!(events(), expected); - - assert_eq!(balances(&11), (1900, 900)); - assert_eq!(balances(&101), (1950, 450)); - - assert_eq!(NodleStaking::total(), 3350); - - assert!(>::get(&11).is_some()); - - assert_eq!(>::get(&(11, 0)).amount_slashed(), &(100)); - assert_eq!(>::get(&(101, 0)).amount_slashed(), &(50)); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(100)], - DisableStrategy::Always, - ); - - let mut new2 = vec![ - Event::Slash(11, 899), - Event::Slash(101, 449), - Event::NominationBelowThreashold(101, 11, 1, 1, 0), - ]; - - expected.append(&mut new2); - assert_eq!(events(), expected); - - assert_eq!(mock::balances(&11), (1001, 1)); - assert_eq!(mock::balances(&101), (1501, 1)); - assert_eq!(NodleStaking::total(), 2002); - - let slashing_spans = >::get(&11).unwrap(); - assert_eq!(slashing_spans.iter().count(), 2); - - // TODO :: Validation of DB instance Clean-off pending - // // reap_stash respects num_slashing_spans so that weight is accurate - // assert_noop!(Staking::reap_stash(Origin::none(), 11, 0), - // Error::::IncorrectSlashingSpans); assert_ok!(Staking::reap_stash(Origin::none(), - // 11, 2)); - - // assert!(::SlashingSpans::get(&11).is_none()); - // assert_eq!(::SpanSlash::get(&(11, 0)).amount_slashed(), &0); - }) -} - -#[test] -fn slash_kicks_validators_not_nominators_and_activate_validator_to_rejoin_pool() { - ExtBuilder::default().num_validators(4).build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(events(), expected); - - // pre-slash balance - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(NodleStaking::total(), 3500); - - assert_eq!(Session::validators(), vec![11, 21, 41]); - - let exposure_11 = NodleStaking::at_stake(NodleStaking::active_session(), &11); - let exposure_21 = NodleStaking::at_stake(NodleStaking::active_session(), &21); - - assert_eq!(exposure_11.total, 1500); - assert_eq!(exposure_21.total, 1000); - - on_offence_now( - &[OffenceDetails { - offender: (11, exposure_11), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - DisableStrategy::Always, - ); - - let mut new1 = vec![Event::Slash(11, 100), Event::Slash(101, 50)]; - - expected.append(&mut new1); - assert_eq!(mock::events(), expected); - - // post-slash balance - assert_eq!(mock::balances(&11), (1900, 900)); - assert_eq!(mock::balances(&101), (1950, 450)); - assert_eq!(NodleStaking::total(), 3350); - - // Validator-11 is deactivated - assert_eq!(NodleStaking::validator_state(&11).unwrap().state, ValidatorStatus::Idle); - - mock::start_active_session(3); - - // ensure validator 11 is not selected for the session 3 & 4 - - let mut new1 = vec![ - Event::ValidatorChosen(3, 21, 1000), - Event::ValidatorChosen(3, 41, 1000), - Event::NewSession(10, 3, 2, 2000), - Event::ValidatorChosen(4, 21, 1000), - Event::ValidatorChosen(4, 41, 1000), - Event::NewSession(15, 4, 2, 2000), - ]; - expected.append(&mut new1); - assert_eq!(events(), expected); - - // activate validator 11 in session 3 - assert_ok!(NodleStaking::validator_bond_more(Origin::signed(11), 10)); - - mock::start_active_session(4); - - // ensure validator 11 is part of session 5 - - // log::trace!("[{:#?}]=> - {:#?}", line!(), mock::events()); - // panic!("Stop-1"); - - // let mut new2 = vec![ - // Event::ValidatorBondedMore(11, 900, 910), - // Event::ValidatorChosen(5, 11, 1410), - // Event::ValidatorChosen(5, 21, 1000), - // Event::ValidatorChosen(5, 41, 1000), - // Event::NewSession(20, 5, 3, 3410), - // ]; - // expected.append(&mut new2); - // assert_eq!(events(), expected); - - let mut new2 = vec![ - Event::ValidatorBondedMore(11, 900, 910), - Event::ValidatorChosen(5, 11, 1360), - Event::ValidatorChosen(5, 21, 1000), - Event::ValidatorChosen(5, 41, 1000), - Event::NewSession(20, 5, 3, 3360), - ]; - expected.append(&mut new2); - assert_eq!(events(), expected); - - assert_eq!(NodleStaking::total(), 3360); - }); -} - -#[test] -fn slashing_nominators_by_span_max() { - ExtBuilder::default().num_validators(4).build_and_execute(|| { - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(101), 21, 500, false,)); - - mock::start_active_session(1); - mock::start_active_session(2); - mock::start_active_session(3); - - let mut expected = vec![ - Event::Nomination(101, 500, 21, 1500), - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1500), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 4000), - Event::ValidatorChosen(3, 11, 1500), - Event::ValidatorChosen(3, 21, 1500), - Event::ValidatorChosen(3, 41, 1000), - Event::NewSession(10, 3, 3, 4000), - Event::ValidatorChosen(4, 11, 1500), - Event::ValidatorChosen(4, 21, 1500), - Event::ValidatorChosen(4, 41, 1000), - Event::NewSession(15, 4, 3, 4000), - ]; - - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(mock::balances(&21), (2000, 1000)); - assert_eq!(mock::balances(&101), (2000, 1000)); - assert_eq!(NodleStaking::total(), 4000); - - let get_span = |account| >::get(&account).unwrap(); - - let exposure_11 = NodleStaking::at_stake(NodleStaking::active_session(), 11); - let exposure_21 = NodleStaking::at_stake(NodleStaking::active_session(), 21); - let nominated_value_11 = exposure_11.nominators.iter().find(|o| o.owner == 101).unwrap().amount; - let nominated_value_21 = exposure_21.nominators.iter().find(|o| o.owner == 101).unwrap().amount; - - // Check slashing - on_offence_in_session( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - 2, - DisableStrategy::Always, - ); - - let mut new1 = vec![Event::Slash(11, 100), Event::Slash(101, 50)]; - - expected.append(&mut new1); - assert_eq!(mock::events(), expected); - - let nominator101_prev_slashed_val = Perbill::from_percent(10) * nominated_value_11; - - assert_eq!(mock::balances(&11), (1900, 900)); - - assert_eq!(mock::balances(&101), (1950, 1000 - nominator101_prev_slashed_val,)); - assert_eq!(NodleStaking::total(), 3850); - - let expected_spans = vec![ - slashing::SlashingSpan { - index: 1, - start: 4, - length: None, - }, - slashing::SlashingSpan { - index: 0, - start: 1, - length: Some(3), - }, - ]; - - assert_eq!(get_span(11).iter().collect::>(), expected_spans); - assert_eq!(get_span(101).iter().collect::>(), expected_spans); - - // second slash: higher era, higher value, same span. - on_offence_in_session( - &[OffenceDetails { - offender: (21, NodleStaking::at_stake(NodleStaking::active_session(), 21)), - reporters: vec![], - }], - &[Perbill::from_percent(30)], - 3, - DisableStrategy::Always, - ); - - // Since on same span for 101, slash_value = 150 - 50 = 100 - let mut new2 = vec![Event::Slash(21, 300), Event::Slash(101, 100)]; - - expected.append(&mut new2); - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&21), (1700, 700)); - assert_eq!(mock::balances(&101), (1850, 850)); - assert_eq!(NodleStaking::total(), 3450); - - let nominator101_prev_slashed_val = - (Perbill::from_percent(30) * nominated_value_21) - nominator101_prev_slashed_val; - - assert_eq!(mock::balances(&101), (1850, 950 - nominator101_prev_slashed_val)); - - let expected_spans = vec![ - slashing::SlashingSpan { - index: 1, - start: 4, - length: None, - }, - slashing::SlashingSpan { - index: 0, - start: 1, - length: Some(3), - }, - ]; - - assert_eq!(get_span(21).iter().collect::>(), expected_spans); - assert_eq!(get_span(101).iter().collect::>(), expected_spans); - - on_offence_in_session( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(20)], - 2, - DisableStrategy::Always, - ); - - // Only Validator-11 is slashed, and Nominator-101 is not slashed since - // Here slash value is less than the Span Max. - let mut new3 = vec![Event::Slash(11, 100)]; - - expected.append(&mut new3); - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&11), (1800, 800)); - assert_eq!(NodleStaking::total(), 3350); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - }); -} - -#[test] -fn slashes_are_summed_across_spans() { - ExtBuilder::default().num_validators(4).build_and_execute(|| { - assert_ok!(NodleStaking::nominator_nominate(Origin::signed(101), 21, 500, false,)); - mock::start_active_session(1); - mock::start_active_session(2); - mock::start_active_session(3); - - let mut expected = vec![ - Event::Nomination(101, 500, 21, 1500), - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1500), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 4000), - Event::ValidatorChosen(3, 11, 1500), - Event::ValidatorChosen(3, 21, 1500), - Event::ValidatorChosen(3, 41, 1000), - Event::NewSession(10, 3, 3, 4000), - Event::ValidatorChosen(4, 11, 1500), - Event::ValidatorChosen(4, 21, 1500), - Event::ValidatorChosen(4, 41, 1000), - Event::NewSession(15, 4, 3, 4000), - ]; - - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(mock::balances(&21), (2000, 1000)); - assert_eq!(mock::balances(&101), (2000, 1000)); - assert_eq!(NodleStaking::total(), 4000); - - let get_span = |account| >::get(&account).unwrap(); - - on_offence_now( - &[OffenceDetails { - offender: (21, NodleStaking::at_stake(NodleStaking::active_session(), 21)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - DisableStrategy::Always, - ); - - let mut new1 = vec![Event::Slash(21, 100), Event::Slash(101, 50)]; - - expected.append(&mut new1); - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&21), (1900, 900)); - assert_eq!(mock::balances(&101), (1950, 950)); - assert_eq!(NodleStaking::total(), 3850); - - let expected_spans = vec![ - slashing::SlashingSpan { - index: 1, - start: 4, - length: None, - }, - slashing::SlashingSpan { - index: 0, - start: 1, - length: Some(3), - }, - ]; - - assert_eq!(get_span(21).iter().collect::>(), expected_spans,); - - assert_ok!(NodleStaking::validator_bond_more(Origin::signed(21), 10)); - - mock::start_active_session(5); - - on_offence_now( - &[OffenceDetails { - offender: (21, NodleStaking::at_stake(NodleStaking::active_session(), 21)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - DisableStrategy::Always, - ); - - let mut new2 = vec![ - Event::ValidatorBondedMore(21, 900, 910), - Event::ValidatorChosen(5, 11, 1500), - Event::ValidatorChosen(5, 21, 1360), - Event::ValidatorChosen(5, 41, 1000), - Event::NewSession(20, 5, 3, 3860), - Event::ValidatorChosen(6, 11, 1500), - Event::ValidatorChosen(6, 21, 1360), - Event::ValidatorChosen(6, 41, 1000), - Event::NewSession(25, 6, 3, 3860), - Event::Slash(21, 91), - Event::Slash(101, 45), - ]; - expected.append(&mut new2); - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&21), (1809, 819)); - assert_eq!(mock::balances(&101), (1905, 905)); - assert_eq!(NodleStaking::total(), 3724); - - let expected_spans = vec![ - slashing::SlashingSpan { - index: 2, - start: 6, - length: None, - }, - slashing::SlashingSpan { - index: 1, - start: 4, - length: Some(2), - }, - slashing::SlashingSpan { - index: 0, - start: 1, - length: Some(3), - }, - ]; - assert_eq!(get_span(21).iter().collect::>(), expected_spans); - - // tst_log!( - // debug, - // "[{:#?}]=> - {:#?}", - // line!(), - // get_span(21).iter().collect::>() - // ); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - }); -} - -#[test] -fn deferred_slashes_are_deferred() { - ExtBuilder::default() - .num_validators(4) - .slash_defer_duration(2) - .build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(NodleStaking::total(), 3500); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - DisableStrategy::Always, - ); - - // Since slash is deffered, - // Ensure deffered unapplied slashing events - let mut new1 = vec![Event::DeferredUnappliedSlash(1, 11)]; - - expected.append(&mut new1); - assert_eq!(mock::events(), expected); - - mock::start_active_session(2); - - let mut new2 = vec![ - Event::ValidatorChosen(3, 21, 1000), - Event::ValidatorChosen(3, 41, 1000), - Event::NewSession(10, 3, 2, 2000), - ]; - - expected.append(&mut new2); - assert_eq!(mock::events(), expected); - assert_eq!(NodleStaking::total(), 3500); - - // Ensure slash occur at start of 3 session ( 1 + 2 [deferred duration] ) - mock::start_active_session(3); - - let mut new3 = vec![ - Event::Slash(11, 100), - Event::Slash(101, 50), - Event::ValidatorChosen(4, 21, 1000), - Event::ValidatorChosen(4, 41, 1000), - Event::NewSession(15, 4, 2, 2000), - ]; - expected.append(&mut new3); - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&11), (1900, 900)); - assert_eq!(mock::balances(&101), (1950, 450)); - assert_eq!(NodleStaking::total(), 3350); - }) -} - -#[test] -fn remove_deferred() { - ExtBuilder::default() - .slash_defer_duration(2) - .num_validators(4) - .build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(NodleStaking::total(), 3500); - - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - DisableStrategy::Always, - ); - - // Since slash is deffered, - // Ensure deffered unapplied slashing events - let mut new1 = vec![Event::DeferredUnappliedSlash(1, 11)]; - - expected.append(&mut new1); - assert_eq!(mock::events(), expected); - - mock::start_active_session(2); - - let mut new2 = vec![ - Event::ValidatorChosen(3, 21, 1000), - Event::ValidatorChosen(3, 41, 1000), - Event::NewSession(10, 3, 2, 2000), - ]; - - expected.append(&mut new2); - assert_eq!(mock::events(), expected); - assert_eq!(NodleStaking::total(), 3500); - - on_offence_in_session( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(1, 11)), - reporters: vec![], - }], - &[Perbill::from_percent(15)], - 1, - DisableStrategy::Always, - ); - - // Since slash is deffered, - // Ensure deffered unapplied slashing events - let mut new3 = vec![Event::DeferredUnappliedSlash(2, 11)]; - - expected.append(&mut new3); - assert_eq!(mock::events(), expected); - - // fails if empty - assert_noop!( - NodleStaking::slash_cancel_deferred(Origin::root(), 1, vec![]), - Error::::EmptyTargets - ); - - assert_ok!(NodleStaking::slash_cancel_deferred(Origin::root(), 1, vec![11],)); - - mock::start_active_session(3); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(NodleStaking::total(), 3500); - - let mut new4 = vec![ - Event::ValidatorChosen(4, 21, 1000), - Event::ValidatorChosen(4, 41, 1000), - Event::NewSession(15, 4, 2, 2000), - ]; - - expected.append(&mut new4); - assert_eq!(mock::events(), expected); - - mock::start_active_session(4); - - // Ensure deffered slash event have fired. - assert_eq!(mock::balances(&11), (1950, 950)); - assert_eq!(mock::balances(&101), (1975, 475)); - - let mut new5 = vec![ - Event::Slash(11, 50), - Event::Slash(101, 25), - Event::ValidatorChosen(5, 21, 1000), - Event::ValidatorChosen(5, 41, 1000), - Event::NewSession(20, 5, 2, 2000), - ]; - - expected.append(&mut new5); - assert_eq!(mock::events(), expected); - assert_eq!(NodleStaking::total(), 3425); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - }) -} - -#[test] -fn remove_multi_deferred() { - ExtBuilder::default() - .num_validators(4) - .slash_defer_duration(2) - .build_and_execute(|| { - mock::start_active_session(1); - - let mut expected = vec![ - Event::ValidatorChosen(2, 11, 1500), - Event::ValidatorChosen(2, 21, 1000), - Event::ValidatorChosen(2, 41, 1000), - Event::NewSession(5, 2, 3, 3500), - ]; - assert_eq!(mock::events(), expected); - - assert_eq!(mock::balances(&11), (2000, 1000)); - assert_eq!(mock::balances(&101), (2000, 500)); - assert_eq!(NodleStaking::total(), 3500); - - // Add 11 to Unapplied Slash Q - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - DisableStrategy::Always, - ); - - // Since slash is deffered, - // Ensure deffered unapplied slashing events - let mut new1 = vec![Event::DeferredUnappliedSlash(1, 11)]; - - expected.append(&mut new1); - assert_eq!(mock::events(), expected); - - // Add 21 to Unapplied Slash Q - on_offence_now( - &[OffenceDetails { - offender: (21, NodleStaking::at_stake(NodleStaking::active_session(), 21)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - DisableStrategy::Always, - ); - - // Since slash is deffered, - // Ensure deffered unapplied slashing events - let mut new2 = vec![Event::DeferredUnappliedSlash(1, 21)]; - - expected.append(&mut new2); - assert_eq!(mock::events(), expected); - - // Add 11 to Unapplied Slash Q [25%] - on_offence_now( - &[OffenceDetails { - offender: (11, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(25)], - DisableStrategy::Always, - ); - - // Since slash is deffered, - // Ensure deffered unapplied slashing events - let mut new3 = vec![Event::DeferredUnappliedSlash(1, 11)]; - - expected.append(&mut new3); - assert_eq!(mock::events(), expected); - - // Add 42 with exposure of 11 to Unapplied Slash Q [25%] - on_offence_now( - &[OffenceDetails { - offender: (41, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(25)], - DisableStrategy::Always, - ); - - // Since slash is deffered, - // Ensure deffered unapplied slashing events - let mut new4 = vec![Event::DeferredUnappliedSlash(1, 41)]; - - expected.append(&mut new4); - assert_eq!(mock::events(), expected); - - // Add 69 with exposure of 11 to Unapplied Slash Q [25%] - on_offence_now( - &[OffenceDetails { - offender: (69, NodleStaking::at_stake(NodleStaking::active_session(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(25)], - DisableStrategy::Always, - ); - - // Since slash is deffered, - // Ensure deffered unapplied slashing events - let mut new5 = vec![Event::DeferredUnappliedSlash(1, 69)]; - - expected.append(&mut new5); - assert_eq!(mock::events(), expected); - - // mock::SLASH_DEFER_DURATION.saturating_add(NodleStaking::active_session()); - // let apply_at = mock::SLASH_DEFER_DURATION - // .with(|v| *v.get() + NodleStaking::active_session()); - - let apply_at = NodleStaking::active_session() + mock::SLASH_DEFER_DURATION.with(|l| *l.borrow()); - - assert_eq!(>::get(&apply_at).len(), 5); - - assert_noop!( - NodleStaking::slash_cancel_deferred(Origin::root(), 1, vec![]), - Error::::EmptyTargets - ); - - assert_noop!( - NodleStaking::slash_cancel_deferred(Origin::root(), apply_at, vec![11]), - Error::::InvalidSessionIndex - ); - - assert_ok!(NodleStaking::slash_cancel_deferred(Origin::root(), 1, vec![11]),); - - assert_eq!(>::get(&apply_at).len(), 3); - - assert_ok!(NodleStaking::slash_cancel_deferred(Origin::root(), 1, vec![69]),); - - assert_eq!(>::get(&apply_at).len(), 2); - - assert_eq!(>::get(&apply_at)[0].validator, 21); - assert_eq!(>::get(&apply_at)[1].validator, 41); - - mock::start_active_session(4); - - let mut new6 = vec![ - Event::NewSession(10, 3, 0, 0), - Event::Slash(21, 100), - Event::Slash(41, 250), - Event::NewSession(15, 4, 0, 0), - Event::NewSession(20, 5, 0, 0), - ]; - - expected.append(&mut new6); - assert_eq!(mock::events(), expected); - - assert_eq!(NodleStaking::total(), 3150); - - // tst_log!(debug, "[{:#?}]=> - {:#?}", line!(), mock::events()); - - // tst_log!( - // debug, - // "[{:#?}]=> - [{:#?}] | [{:#?}]", - // line!(), - // apply_at, - // >::get(&apply_at) - // ); - }) -} diff --git a/pallets/staking/src/types.rs b/pallets/staking/src/types.rs index e9ef1869feb..ceeb97219ba 100644 --- a/pallets/staking/src/types.rs +++ b/pallets/staking/src/types.rs @@ -18,19 +18,18 @@ use super::{ActiveSession, AtStake, BalanceOf, Config, Error, Pallet}; use crate::set::OrderedSet; -use codec::{Codec, Decode, Encode, HasCompact}; +use codec::{Decode, Encode}; use derivative::Derivative; use frame_support::{bounded_vec, pallet_prelude::MaxEncodedLen, traits::Get, BoundedVec}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Convert, Saturating, Zero}, + traits::{Convert, Saturating, Zero}, RuntimeDebug, }; use sp_staking::SessionIndex; use sp_std::marker::PhantomData; use sp_std::{ cmp::{max, Ordering}, - convert::From, fmt::Debug, prelude::*, }; @@ -99,7 +98,7 @@ pub struct UnlockChunk { pub(crate) type StakeReward = UnlockChunk; -#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] +#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] /// The activity status of the validator pub enum ValidatorStatus { /// Committed to be online and producing valid blocks @@ -122,10 +121,10 @@ impl Default for ValidatorStatus { #[derive(Decode, Encode, TypeInfo)] #[scale_info(skip_type_params(T, MaxNominators, MaxUnlock))] pub struct Validator, MaxUnlock: Get> { - pub id: ::AccountId, + pub id: T::AccountId, pub bond: BalanceOf, pub nomi_bond_total: BalanceOf, - pub nominators: OrderedSet::AccountId, BalanceOf>, MaxNominators>, + pub nominators: OrderedSet>, MaxNominators>, pub total: BalanceOf, pub state: ValidatorStatus, pub unlocking: BoundedVec>, MaxUnlock>, @@ -148,14 +147,14 @@ where MaxNominators: Get, MaxUnlock: Get, { - pub fn new(id: ::AccountId, bond: BalanceOf) -> Self { + pub fn new(id: T::AccountId, bond: BalanceOf) -> Self { let total = bond; let unlocking: BoundedVec>, MaxUnlock> = bounded_vec![]; Validator { id, bond, nomi_bond_total: Zero::zero(), - nominators: OrderedSet::::AccountId, BalanceOf>, MaxNominators>::new(), + nominators: OrderedSet::>, MaxNominators>::new(), total, state: ValidatorStatus::default(), // default active unlocking: unlocking, @@ -185,12 +184,8 @@ where } } - pub fn inc_nominator( - &mut self, - nominator: ::AccountId, - more: BalanceOf, - ) -> Result<(), Error> { - let mut nominators: Vec::AccountId, BalanceOf>> = + pub fn inc_nominator(&mut self, nominator: T::AccountId, more: BalanceOf) -> Result<(), Error> { + let mut nominators: Vec>> = self.nominators.get_inner().map_err(|_| >::OrderedSetFailure)?; if let Ok(loc) = nominators.binary_search(&Bond::from_owner(nominator)) { @@ -208,15 +203,11 @@ where Ok(()) } - pub fn dec_nominator( - &mut self, - nominator: ::AccountId, - less: BalanceOf, - ) -> Result<(), Error> { - let mut nominators: Vec::AccountId, BalanceOf>> = + pub fn dec_nominator(&mut self, nominator: &T::AccountId, less: BalanceOf) -> Result<(), Error> { + let mut nominators: Vec>> = self.nominators.get_inner().map_err(|_| >::OrderedSetFailure)?; - if let Ok(loc) = nominators.binary_search(&Bond::from_owner(nominator)) { + if let Ok(loc) = nominators.binary_search(&Bond::from_owner(nominator.clone())) { let nom_bond = &mut nominators[loc]; nom_bond.amount = nom_bond.amount.saturating_sub(less); self.nomi_bond_total = self.nomi_bond_total.saturating_sub(less); @@ -240,7 +231,7 @@ where } pub fn build_snapshot(&self) -> Result, Error> { - let nominators: Vec::AccountId, BalanceOf>> = + let nominators: Vec>> = self.nominators.get_inner().map_err(|_| >::OrderedSetFailure)?; // let nominators_snapshot = BoundedVec::try_from(nominators).map_err(|_| @@ -255,6 +246,75 @@ where } } +impl Validator +where + T: Config, + MaxNominators: Get, + MaxUnlock: Get, +{ + /// Slash the validator for a given amount of balance. This can grow the value + /// of the slash in the case that the validator has less than `minimum_balance` + /// active funds. Returns the amount of funds actually slashed. + /// + /// Slashes from `active` funds first, and then `unlocking`, starting with the + /// chunks that are closest to unlocking. + pub(crate) fn slash(&mut self, mut value: BalanceOf, minimum_balance: BalanceOf) -> BalanceOf { + let pre_total = self.total; + let total = &mut self.total; + let active = &mut self.bond; + + let slash_out_of = |total_remaining: &mut BalanceOf, target: &mut BalanceOf, value: &mut BalanceOf| { + let mut slash_from_target = (*value).min(*target); + + if !slash_from_target.is_zero() { + *target = target.saturating_sub(slash_from_target); + + // Make sure not drop below ED + if *target <= minimum_balance { + let diff_val = minimum_balance.saturating_sub(*target); + *target = target.saturating_add(diff_val); + slash_from_target = slash_from_target.saturating_sub(diff_val); + } + *total_remaining = total_remaining.saturating_sub(slash_from_target); + *value = value.saturating_sub(slash_from_target); + } + }; + + slash_out_of(total, active, &mut value); + + let i = self + .unlocking + .iter_mut() + .map(|chunk| { + slash_out_of(total, &mut chunk.value, &mut value); + chunk.value + }) + .take_while(|value| value.is_zero()) // take all fully-consumed chunks out. + .count(); + + // kill all drained chunks. + let _ = self.unlocking.drain(..i); + + pre_total.saturating_sub(*total) + } + /// Remove entries from `unlocking` that are sufficiently old and reduce the + /// total by the sum of their balances. + pub fn consolidate_unlocked(&mut self, current_session: SessionIndex) -> BalanceOf { + let mut total = self.total; + self.unlocking.retain(|&chunk| { + if chunk.session_idx > current_session { + true + } else { + total = total.saturating_sub(chunk.value); + false + } + }); + let unlocked_val = self.total.saturating_sub(total); + self.total = total; + unlocked_val + } +} + /// Snapshot of validator state at the start of the round for which they are selected #[derive(Derivative)] #[derivative(Debug(bound = "T: Debug"), Clone(bound = "T: Clone"))] @@ -262,7 +322,7 @@ where #[scale_info(skip_type_params(T, MaxNominators))] pub struct ValidatorSnapshot> { pub bond: BalanceOf, - pub nominators: Vec::AccountId, BalanceOf>>, + pub nominators: Vec>>, pub total: BalanceOf, pub stub: PhantomData, } @@ -283,10 +343,9 @@ where MaxNominators: Get, { fn default() -> Self { - let inner: Vec::AccountId, BalanceOf>> = - Vec::with_capacity(MaxNominators::get() as usize); + let inner: Vec>> = Vec::with_capacity(MaxNominators::get() as usize); - // let nominators: BoundedVec::AccountId, BalanceOf>, + // let nominators: BoundedVec>, // MaxNominators> = BoundedVec::try_from(inner).expect("ValidatorSnapshot Failed To Create // Default"); @@ -363,7 +422,7 @@ impl> #[derive(Decode, Encode, TypeInfo)] #[scale_info(skip_type_params(T, MaxNomination, MaxUnlock))] pub struct Nominator, MaxUnlock: Get> { - pub nominations: OrderedSet::AccountId, BalanceOf>, MaxNomination>, + pub nominations: OrderedSet>, MaxNomination>, pub total: BalanceOf, pub active_bond: BalanceOf, pub frozen_bond: BalanceOf, @@ -387,7 +446,7 @@ where MaxNomination: Get, MaxUnlock: Get, { - pub fn new(validator: ::AccountId, amount: BalanceOf) -> Result> { + pub fn new(validator: T::AccountId, amount: BalanceOf) -> Result> { let unlocking: BoundedVec>, MaxUnlock> = bounded_vec![]; let nominations = OrderedSet::try_from(vec![Bond { @@ -406,7 +465,7 @@ where } pub fn add_nomination( &mut self, - bond: Bond<::AccountId, BalanceOf>, + bond: Bond>, unfreeze_bond: bool, ) -> Result> { let amt = bond.amount; @@ -427,11 +486,7 @@ where } // Returns Some(remaining balance), must be more than MinNominatorStake // Returns None if nomination not found - pub fn rm_nomination( - &mut self, - validator: ::AccountId, - freeze_bond: bool, - ) -> Result, Error> { + pub fn rm_nomination(&mut self, validator: &T::AccountId, freeze_bond: bool) -> Result, Error> { let mut amt: Option> = None; let nominations_inner = self @@ -439,10 +494,10 @@ where .get_inner() .map_err(|_| >::OrderedSetFailure)?; - let nominations: Vec::AccountId, BalanceOf>> = nominations_inner + let nominations: Vec>> = nominations_inner .iter() .filter_map(|x| { - if x.owner == validator { + if x.owner == *validator { amt = Some(x.amount); None } else { @@ -474,7 +529,7 @@ where // Returns None if nomination not found pub fn inc_nomination( &mut self, - validator: ::AccountId, + validator: T::AccountId, more: BalanceOf, unfreeze_bond: bool, ) -> Result, Error> { @@ -500,11 +555,7 @@ where return Err(>::NominationDNE); } } - pub fn dec_nomination( - &mut self, - validator: ::AccountId, - less: BalanceOf, - ) -> Result, Error> { + pub fn dec_nomination(&mut self, validator: T::AccountId, less: BalanceOf) -> Result, Error> { let mut nominations_inner = self .nominations .get_inner() @@ -524,3 +575,83 @@ where } } } + +impl Nominator +where + T: Config, + MaxNomination: Get, + MaxUnlock: Get, +{ + /// Slash the validator for a given amount of balance. This can grow the value + /// of the slash in the case that the validator has less than `minimum_balance` + /// active funds. Returns the amount of funds actually slashed. + /// + /// Slashes from `active` funds first, and then `unlocking`, starting with the + /// chunks that are closest to unlocking. + pub(crate) fn slash_nomination( + &mut self, + validator: &T::AccountId, + mut value: BalanceOf, + minimum_balance: BalanceOf, + ) -> BalanceOf { + let pre_total = self.total; + let total = &mut self.total; + let pre_active_bond = self.active_bond; + let active_bond = &mut self.active_bond; + + let slash_out_of = |total_remaining: &mut BalanceOf, target: &mut BalanceOf, value: &mut BalanceOf| { + let mut slash_from_target = (*value).min(*target); + + if !slash_from_target.is_zero() { + *target = target.saturating_sub(slash_from_target); + *total_remaining = total_remaining.saturating_sub(slash_from_target); + + // Make sure not drop below ED + if *total_remaining <= minimum_balance { + let diff_val = minimum_balance.saturating_sub(*total_remaining); + *target = target.saturating_add(diff_val); + *total_remaining = total_remaining.saturating_add(diff_val); + slash_from_target = slash_from_target.saturating_sub(diff_val); + } + *value = value.saturating_sub(slash_from_target); + } + }; + + if let Ok(loc) = self.nominations.0.binary_search(&Bond::from_owner(validator.clone())) { + let nom_bond = &mut self.nominations.0[loc]; + slash_out_of(active_bond, &mut nom_bond.amount, &mut value); + }; + + *total = total.saturating_sub(pre_active_bond.saturating_sub(*active_bond)); + + let i = self + .unlocking + .iter_mut() + .map(|chunk| { + slash_out_of(total, &mut chunk.value, &mut value); + chunk.value + }) + .take_while(|value| value.is_zero()) // take all fully-consumed chunks out. + .count(); + + // kill all drained chunks. + let _ = self.unlocking.drain(..i); + pre_total.saturating_sub(*total) + } + /// Remove entries from `unlocking` that are sufficiently old and reduce the + /// total by the sum of their balances. + pub fn consolidate_unlocked(&mut self, current_session: SessionIndex) -> BalanceOf { + let mut total = self.total; + self.unlocking.retain(|&chunk| { + if chunk.session_idx > current_session { + true + } else { + total = total.saturating_sub(chunk.value); + false + } + }); + let unlocked_val = self.total.saturating_sub(total); + self.total = total; + unlocked_val + } +}