diff --git a/Cargo.lock b/Cargo.lock index d7c32f5aa84..42812121b9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6020,6 +6020,7 @@ dependencies = [ name = "pallet-staking" version = "2.0.20" dependencies = [ + "derivative", "frame-benchmarking", "frame-support", "frame-system", diff --git a/pallets/staking/Cargo.toml b/pallets/staking/Cargo.toml index 91b5f656519..2a0f68d4083 100644 --- a/pallets/staking/Cargo.toml +++ b/pallets/staking/Cargo.toml @@ -39,6 +39,7 @@ log = { version = "0.4.14", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +derivative = "2.2.0" frame-benchmarking = { git = "https://github.com/paritytech/substrate", default-features = false, optional = true , branch = "polkadot-v0.9.20" } frame-support = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.20" } frame-system = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.20" } diff --git a/pallets/staking/src/hooks.rs b/pallets/staking/src/hooks.rs index d7410cb8113..0c305ecb39c 100644 --- a/pallets/staking/src/hooks.rs +++ b/pallets/staking/src/hooks.rs @@ -20,11 +20,12 @@ use super::{ ActiveSession, BalanceOf, BondedSessions, Config, Event, NegativeImbalanceOf, Pallet, SessionAccumulatedBalance, SessionValidatorReward, SlashRewardProportion, Staked, Store, Total, }; -use crate::slashing; +// use crate::slashing; use crate::types::{ValidatorSnapshot, ValidatorSnapshotOf}; use frame_support::{ pallet_prelude::*, traits::{Currency, Get, Imbalance, OnUnbalanced}, + BoundedVec, }; use frame_system::{self as system}; use pallet_session::historical; @@ -56,124 +57,6 @@ 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) = Self::select_session_validators(new_index); - - // 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()) - } - 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| { - bonded.push(start_index); - - 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 - .iter() - .take_while(|&&session_idx| session_idx < first_kept) - .count(); - - for prune_session in bonded.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` @@ -194,8 +77,8 @@ impl SessionInterface<::AccountId> for T where T: pallet_session::Config::AccountId>, T: pallet_session::historical::Config< - FullIdentification = ValidatorSnapshot<::AccountId, BalanceOf>, - FullIdentificationOf = ValidatorSnapshotOf, + FullIdentification = ValidatorSnapshot, + FullIdentificationOf = ValidatorSnapshotOf, >, T::SessionHandler: pallet_session::SessionHandler<::AccountId>, T::SessionManager: pallet_session::SessionManager<::AccountId>, @@ -216,125 +99,3 @@ 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<::AccountId, BalanceOf>, - 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 unapplied = slashing::compute_slash::(slashing::SlashParams { - controller, - slash: *slash_fraction, - exposure, - slash_session, - window_start, - now: active_session, - reward_proportion, - disable_strategy, - }); - - if let Some(mut unapplied) = unapplied { - let nominators_len = unapplied.others.len() as u64; - let reporters_len = details.reporters.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 = details.reporters.clone(); - if slash_defer_duration == 0 { - // apply right away. - slashing::apply_slash::(unapplied); - - 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); - - ::UnappliedSlashes::mutate(apply_at, |for_later| for_later.push(unapplied.clone())); - - >::deposit_event(Event::DeferredUnappliedSlash(active_session, unapplied.validator)); - - add_db_reads_writes(1, 1); - } - } else { - log::trace!("on_offence:[{:#?}] - NOP", line!(),); - add_db_reads_writes(4 /* fetch_spans */, 5 /* kick_out_if_recent */); - } - } - consumed_weight - } -} diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs index ef9af55b23d..8ad9d292485 100644 --- a/pallets/staking/src/lib.rs +++ b/pallets/staking/src/lib.rs @@ -21,40 +21,27 @@ #![cfg_attr(not(feature = "std"), no_std)] -mod benchmarking; - -#[cfg(test)] -mod mock; - -#[cfg(test)] -mod tests; - -mod set; -pub mod weights; +// #[cfg(test)] +// mod mock; use frame_support::pallet; -pub(crate) mod hooks; -mod migrations; -pub(crate) mod slashing; -pub(crate) mod types; - -#[cfg(feature = "std")] -use frame_support::traits::GenesisBuild; +mod hooks; +mod set; +mod types; pub use pallet::*; #[pallet] pub mod pallet { use super::*; - use crate::set::OrderedSet; - use frame_support::traits::OnRuntimeUpgrade; use frame_support::{ + bounded_vec, pallet_prelude::*, traits::{ - Currency, ExistenceRequirement, Get, Imbalance, LockIdentifier, LockableCurrency, OnUnbalanced, + Currency, ExistenceRequirement, Get, Imbalance, LockIdentifier, LockableCurrency, OnUnbalanced, Polling, ValidatorRegistration, WithdrawReasons, }, - PalletId, + BoundedVec, PalletId, }; use frame_system::pallet_prelude::*; use sp_runtime::{ @@ -64,15 +51,8 @@ pub mod pallet { use sp_staking::SessionIndex; use sp_std::{convert::From, prelude::*}; - pub use weights::WeightInfo; - - use types::{Bond, Nominator, RewardPoint, SpanIndex, StakeReward, UnappliedSlash, UnlockChunk, Validator}; - - pub use types::{ValidatorSnapshot, ValidatorSnapshotOf}; - - pub use hooks::{SessionInterface, StashOf}; - - pub(crate) type StakingInvulnerables = Vec<::AccountId>; + use set::OrderedSet; + use types::{Bond, Nominator, RewardPoint, StakeReward, UnlockChunk, Validator, ValidatorSnapshot}; pub(crate) type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -85,154 +65,39 @@ 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; - /// Number of sessions that slashes are deferred by, after computation. - type SlashDeferDuration: Get; - /// Minimum number of selected validators every round - type MinSelectedValidators: Get; + /// Maximum validators allowed to join the pool. + #[pallet::constant] + type DefaultStakingMaxValidators: Get; /// Maximum nominators per validator type MaxNominatorsPerValidator: Get; /// Maximum validators per nominator + #[pallet::constant] type MaxValidatorPerNominator: Get; - /// Fee due to validators, set at genesis - type DefaultValidatorFee: Get; - /// Default Slash reward propostion, set at genesis - type DefaultSlashRewardProportion: Get; - /// The proportion of the slashing reward to be paid out on the first slashing detection. - type DefaultSlashRewardFraction: Get; - /// Maximum validators allowed to join the pool. - type DefaultStakingMaxValidators: Get; - /// Minimum stake required for any account to be in `SelectedCandidates` for the session - type DefaultStakingMinStakeSessionSelection: Get>; - /// Minimum stake required for any account to be a validator candidate - type DefaultStakingMinValidatorBond: Get>; - /// Minimum stake for any registered on-chain account to nominate - type DefaultStakingMinNominationChillThreshold: Get>; - /// Minimum stake for any registered on-chain account to become a nominator - 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; + /// staking pallet Lock Identifier used for set_lock() + #[pallet::constant] + type StakingLockId: Get; /// 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() - type StakingLockId: Get; /// Max number of unbond request supported by queue - type MaxChunkUnlock: 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::constant] + type MaxChunkUnlock: Get + MaxEncodedLen + Clone; } #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] - #[pallet::storage_version(migrations::v1::STORAGE_VERSION)] - #[pallet::without_storage_info] pub struct Pallet(PhantomData); #[pallet::hooks] - impl Hooks> for Pallet { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<(), &'static str> { - migrations::v1::PoAToStaking::::pre_upgrade() - } - - fn on_runtime_upgrade() -> frame_support::weights::Weight { - migrations::v1::PoAToStaking::::on_runtime_upgrade() - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade() -> Result<(), &'static str> { - migrations::v1::PoAToStaking::::post_upgrade() - } - } + impl Hooks> for Pallet {} #[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)?; - >::put(&invulnerables); - Self::deposit_event(Event::NewInvulnerables(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(T::WeightInfo::validator_join_pool())] + #[pallet::weight(1000)] pub fn validator_join_pool(origin: OriginFor, bond: BalanceOf) -> DispatchResultWithPostInfo { log::debug!("validator_join_pool:[{:#?}] - Entry!!!", line!(),); @@ -255,16 +120,19 @@ pub mod pallet { let mut validators = >::get(); ensure!( - validators.0.len() < Self::staking_max_validators() as usize, + validators.len().map_err(|_| >::OrderedSetFailure)? < Self::staking_max_validators() as usize, >::ValidatorPoolFull ); - ensure!( - validators.insert(Bond { + + let status = validators + .insert(Bond { owner: acc.clone(), - amount: bond - }), - >::ValidatorExists - ); + amount: bond, + }) + .map_err(|_| >::OrderedSetFailure)?; + + ensure!(status, >::ValidatorExists); + log::debug!("validator_join_pool:[{:#?}]", line!()); let validator_free_balance = T::Currency::free_balance(&acc); @@ -294,7 +162,7 @@ pub mod pallet { /// the account is immediately removed from the validator pool /// to prevent selection as a validator, but unbonding /// is executed with a delay of `BondedDuration` rounds. - #[pallet::weight(T::WeightInfo::validator_exit_pool())] + #[pallet::weight(1000)] pub fn validator_exit_pool(origin: OriginFor) -> DispatchResultWithPostInfo { let validator = ensure_signed(origin)?; @@ -330,7 +198,8 @@ pub mod pallet { Ok(().into()) } /// Bond more for validator - #[pallet::weight(T::WeightInfo::validator_bond_more())] + // #[pallet::weight(T::WeightInfo::validator_bond_more())] + #[pallet::weight(1000)] pub fn validator_bond_more(origin: OriginFor, more: BalanceOf) -> DispatchResultWithPostInfo { let validator = ensure_signed(origin)?; @@ -369,12 +238,13 @@ pub mod pallet { } }); - Self::validator_stake_reconciliation(&validator); + // Self::validator_stake_reconciliation(&validator); Ok(().into()) } /// Bond less for validator - #[pallet::weight(T::WeightInfo::validator_bond_less())] + // #[pallet::weight(T::WeightInfo::validator_bond_less())] + #[pallet::weight(1000)] pub fn validator_bond_less(origin: OriginFor, less: BalanceOf) -> DispatchResultWithPostInfo { let validator = ensure_signed(origin)?; @@ -390,7 +260,7 @@ pub mod pallet { >::ValidatorBondBelowMin ); ensure!( - state.unlocking.len() < T::MaxChunkUnlock::get(), + (state.unlocking.len() as u32) < T::MaxChunkUnlock::get(), >::NoMoreChunks, ); @@ -406,7 +276,7 @@ pub mod pallet { >::mutate(|x| *x = x.saturating_sub(less)); // T::Currency::unreserve(&validator, less); - state.unlocking.push(UnlockChunk { + state.unlocking.try_push(UnlockChunk { value: less, session_idx: Self::active_session().saturating_add(T::BondedDuration::get()), }); @@ -416,9 +286,11 @@ pub mod pallet { } Ok(().into()) } + /// If caller is not a nominator, then join the set of nominators /// If caller is a nominator, then makes nomination to change their nomination state - #[pallet::weight(T::WeightInfo::nominator_nominate())] + // #[pallet::weight(T::WeightInfo::nominator_nominate())] + #[pallet::weight(1000)] pub fn nominator_nominate( origin: OriginFor, validator: T::AccountId, @@ -439,7 +311,7 @@ pub mod pallet { Self::nominator_state(&nominator_acc).ok_or(>::NominatorDNE)? } else { do_add_nomination = false; - Nominator::new(validator.clone(), amount) + Nominator::new(validator.clone(), amount)? }; let amount = if unfreeze_bond { @@ -453,13 +325,18 @@ pub mod pallet { >::NominationBelowMin ); + let nominations_inner: Vec>> = nominator_state + .nominations + .get_inner() + .map_err(|_| >::OrderedSetFailure)?; + ensure!( - (nominator_state.nominations.0.len() as u32) < T::MaxValidatorPerNominator::get(), + (nominations_inner.len() as u32) < T::MaxValidatorPerNominator::get(), >::ExceedMaxValidatorPerNom, ); ensure!( - (validator_state.nominators.0.len() as u32) < T::MaxNominatorsPerValidator::get(), + (nominations_inner.len() as u32) < T::MaxNominatorsPerValidator::get(), >::TooManyNominators, ); @@ -470,16 +347,14 @@ pub mod pallet { nominator_free_balance >= nominator_state.total.saturating_add(amount), >::InsufficientBalance ); - ensure!( - nominator_state.add_nomination( - Bond { - owner: validator.clone(), - amount, - }, - unfreeze_bond, - ), - >::AlreadyNominatedValidator, - ); + + nominator_state.add_nomination( + Bond { + owner: validator.clone(), + amount, + }, + unfreeze_bond, + )?; } else { ensure!(nominator_free_balance >= amount, >::InsufficientBalance); } @@ -493,10 +368,13 @@ pub mod pallet { owner: nominator_acc.clone(), amount, }; - ensure!( - validator_state.nominators.insert(nomination), - >::NominatorExists, - ); + + let insert_status = validator_state + .nominators + .insert(nomination) + .map_err(|_| >::NominationOverflow)?; + + ensure!(insert_status, >::NominatorExists); T::Currency::set_lock( T::StakingLockId::get(), @@ -523,32 +401,46 @@ pub mod pallet { Ok(().into()) } /// Revoke an existing nomination - #[pallet::weight(T::WeightInfo::nominator_denominate())] + // #[pallet::weight(T::WeightInfo::nominator_denominate())] + #[pallet::weight(1000)] pub fn nominator_denominate(origin: OriginFor, validator: T::AccountId) -> DispatchResultWithPostInfo { let acc = ensure_signed(origin)?; - let nominator = >::get(&acc).ok_or(>::NominatorDNE)?; + let nominator_state = >::get(&acc).ok_or(>::NominatorDNE)?; - let do_force = nominator.nominations.0.len() == 1; + let nominations_inner: Vec>> = nominator_state + .nominations + .get_inner() + .map_err(|_| >::OrderedSetFailure)?; + + let do_force = nominations_inner.len() == 1; 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())] + // #[pallet::weight(T::WeightInfo::nominator_denominate_all())] + #[pallet::weight(1000)] pub fn nominator_denominate_all(origin: OriginFor) -> DispatchResultWithPostInfo { let acc = ensure_signed(origin)?; let nominator = >::get(&acc).ok_or(>::NominatorDNE)?; - for bond in nominator.nominations.0 { + let nominator_state = >::get(&acc).ok_or(>::NominatorDNE)?; + + let nominations_inner: Vec>> = nominator_state + .nominations + .get_inner() + .map_err(|_| >::OrderedSetFailure)?; + + for bond in nominations_inner { Self::nominator_revokes_validator(acc.clone(), bond.owner.clone(), true)?; } Ok(().into()) } /// Bond more for nominators with respect to a specific validator - #[pallet::weight(T::WeightInfo::nominator_bond_more())] + // #[pallet::weight(T::WeightInfo::nominator_bond_more())] + #[pallet::weight(1000)] pub fn nominator_bond_more( origin: OriginFor, validator: T::AccountId, @@ -565,9 +457,7 @@ pub mod pallet { more }; - let new_nomination_bond = nominations - .inc_nomination(validator.clone(), more, unfreeze_bond) - .ok_or(>::NominationDNE)?; + let new_nomination_bond = nominations.inc_nomination(validator.clone(), more, unfreeze_bond)?; ensure!( new_nomination_bond >= >::get(), @@ -606,7 +496,8 @@ pub mod pallet { Ok(().into()) } /// Bond less for nominators with respect to a specific nominated validator - #[pallet::weight(T::WeightInfo::nominator_bond_less())] + // #[pallet::weight(T::WeightInfo::nominator_bond_less())] + #[pallet::weight(1000)] pub fn nominator_bond_less( origin: OriginFor, validator: T::AccountId, @@ -614,13 +505,7 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let nominator = ensure_signed(origin)?; let mut nominations = >::get(&nominator).ok_or(>::NominatorDNE)?; - let remaining = nominations.dec_nomination(validator.clone(), less).map_err(|err_str| { - if err_str == "Underflow" { - >::Underflow - } else { - >::NominationDNE - } - })?; + let remaining = nominations.dec_nomination(validator.clone(), less)?; ensure!( remaining >= >::get(), @@ -632,13 +517,13 @@ pub mod pallet { ); ensure!( - nominations.unlocking.len() < T::MaxChunkUnlock::get(), + (nominations.unlocking.len() as u32) < T::MaxChunkUnlock::get(), >::NoMoreChunks, ); let mut validator_state = >::get(&validator).ok_or(>::ValidatorDNE)?; - nominations.unlocking.push(UnlockChunk { + nominations.unlocking.try_push(UnlockChunk { value: less, session_idx: Self::active_session().saturating_add(T::BondedDuration::get()), }); @@ -658,8 +543,8 @@ pub mod pallet { Ok(().into()) } - - #[pallet::weight(T::WeightInfo::nominator_move_nomination())] + // #[pallet::weight(T::WeightInfo::nominator_move_nomination())] + #[pallet::weight(1000)] pub fn nominator_move_nomination( origin: OriginFor, from_validator: T::AccountId, @@ -682,23 +567,31 @@ pub mod pallet { |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)?; + ensure!( - (nominator_state.nominations.0.len() as u32) <= T::MaxValidatorPerNominator::get(), + (nominations_inner.len() as u32) <= T::MaxValidatorPerNominator::get(), >::ExceedMaxValidatorPerNom, ); 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)?; + ensure!( - (to_validator_state.nominators.0.len() as u32) < T::MaxNominatorsPerValidator::get(), + (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) - .ok_or(>::NominationDNE)?; + let remaining = nominator_state.rm_nomination(from_validator.clone(), false)?; let mut total_nomination_amount = old_active_bond.saturating_sub(remaining); nominator_state.total = nominator_state.total.saturating_sub(total_nomination_amount); @@ -721,13 +614,15 @@ pub mod pallet { >::InsufficientBalance ); - if nominator_state.add_nomination( + 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(), @@ -737,9 +632,11 @@ pub mod pallet { 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) - .ok_or(>::NominationDNE)?; + let _ = nominator_state.inc_nomination( + to_validator.clone(), + total_nomination_amount, + unfreeze_bond, + )?; to_validator_state.inc_nominator(nominator_acc.clone(), total_nomination_amount); } @@ -760,7 +657,7 @@ pub mod pallet { } // Already ensured nominator is part of validator state. // So ignoring the return value. - let _ = Self::nominator_leaves_validator(nominator_acc.clone(), from_validator.clone()); + 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()); @@ -784,209 +681,11 @@ pub mod pallet { Ok(().into()) } - - #[pallet::weight(T::WeightInfo::unbond_frozen())] - pub fn unbond_frozen(origin: OriginFor) -> DispatchResultWithPostInfo { - let nominator_acc = ensure_signed(origin)?; - - >::try_mutate_exists( - nominator_acc.clone(), - |maybe_nominator| -> DispatchResultWithPostInfo { - let nominator_state = maybe_nominator.as_mut().ok_or(>::NominatorDNE)?; - - let pre_unfrozen_balance = nominator_state.frozen_bond; - let old_total = nominator_state.total; - - if let Some(_unfrozen_balance) = nominator_state.unbond_frozen() { - T::Currency::set_lock( - T::StakingLockId::get(), - &nominator_acc, - nominator_state.total, - WithdrawReasons::all(), - ); - }; - - Self::deposit_event(Event::NominationUnbondFrozen( - nominator_acc.clone(), - pre_unfrozen_balance, - nominator_state.active_bond, - nominator_state.total, - )); - - if nominator_state.nominations.0.len().is_zero() { - T::Currency::remove_lock(T::StakingLockId::get(), &nominator_acc); - *maybe_nominator = None; - Self::deposit_event(Event::NominatorLeft(nominator_acc.clone(), old_total)); - } - - Ok(().into()) - }, - )?; - Ok(().into()) - } - - #[pallet::weight(T::WeightInfo::withdraw_unbonded())] - pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResultWithPostInfo { - let acc = ensure_signed(origin)?; - - let curr_session = Self::active_session(); - let mut unlocked_bal = Zero::zero(); - let mut new_total = Zero::zero(); - - if Self::is_validator(&acc) { - >::mutate(&acc, |maybe_validator| { - if let Some(state) = maybe_validator { - let pre_total = state.total.saturating_sub(state.nomi_bond_total); - unlocked_bal = state.consolidate_unlocked(curr_session); - new_total = pre_total.saturating_sub(unlocked_bal); - } - }); - - if unlocked_bal > Zero::zero() { - T::Currency::set_lock(T::StakingLockId::get(), &acc, new_total, WithdrawReasons::all()); - } - - Self::deposit_event(Event::Withdrawn(acc, unlocked_bal)); - } else if Self::is_nominator(&acc) { - if let Some(mut nominator_state) = Self::nominator_state(&acc) { - let old_total = nominator_state.total; - unlocked_bal = nominator_state.consolidate_unlocked(curr_session); - new_total = nominator_state.total; - - if unlocked_bal > Zero::zero() { - T::Currency::set_lock(T::StakingLockId::get(), &acc, new_total, WithdrawReasons::all()); - } - - Self::deposit_event(Event::Withdrawn(acc.clone(), unlocked_bal)); - - if nominator_state.nominations.0.len().is_zero() { - T::Currency::remove_lock(T::StakingLockId::get(), &acc); - let _ = Self::kill_state_info(&acc); - Self::deposit_event(Event::NominatorLeft(acc, old_total)); - } else { - >::insert(acc, nominator_state); - } - } - } - - Ok(().into()) - } - - #[pallet::weight(T::WeightInfo::withdraw_staking_rewards())] - pub fn withdraw_staking_rewards(origin: OriginFor) -> DispatchResultWithPostInfo { - let acc = ensure_signed(origin)?; - - >::try_mutate_exists(&acc, |maybe_rewards| -> DispatchResultWithPostInfo { - let rewards = maybe_rewards.as_mut().ok_or(>::RewardsDNE)?; - - let mut total_rewards: BalanceOf = Zero::zero(); - - // Iterate over the reward gains for the given account. - for reward in rewards.iter() { - total_rewards = total_rewards.saturating_add(reward.value); - } - - // deposit the reward gain to - if total_rewards > T::Currency::minimum_balance() { - if let Ok(imb) = T::Currency::deposit_into_existing(&acc, total_rewards) { - *maybe_rewards = None; - Self::deposit_event(Event::Rewarded(acc.clone(), imb.peek())); - } - } else { - // staking rewards are below ED - Self::deposit_event(Event::Rewarded(acc.clone(), Zero::zero())); - } - Ok(().into()) - })?; - - Ok(().into()) - } - - /// Cancel enactment of a deferred slash. - /// - /// Can be called by the `T::SlashCancelOrigin`. - /// - /// Parameters: session index and validator list of the slashes for that session to kill. - #[pallet::weight(T::WeightInfo::slash_cancel_deferred(*session_idx as u32, controllers.len() as u32))] - pub fn slash_cancel_deferred( - origin: OriginFor, - session_idx: SessionIndex, - controllers: Vec, - ) -> DispatchResultWithPostInfo { - T::CancelOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?; - - let apply_at = session_idx.saturating_add(T::SlashDeferDuration::get()); - - ensure!(!controllers.is_empty(), >::EmptyTargets); - ensure!( - >::contains_key(apply_at), - >::InvalidSessionIndex - ); - - >::mutate(&apply_at, |unapplied| { - for controller_acc in controllers { - unapplied.retain(|ustat| ustat.validator != controller_acc); - } - }); - Ok(().into()) - } - } - - #[pallet::error] - pub enum Error { - /// Not a validator account. - ValidatorDNE, - /// Not a nominator account. - NominatorDNE, - /// Validator pool full. - ValidatorPoolFull, - /// Validator account already part of validator pool. - ValidatorExists, - /// Nominator account already part of nominator pool. - NominatorExists, - /// Low free balance in caller account. - InsufficientBalance, - /// Rewards don't exist. - RewardsDNE, - /// Validator bond is less than `MinValidatorPoolStake` value. - ValidatorBondBelowMin, - /// Nominator bond is less than `MinNominatorStake` value. - NominatorBondBelowMin, - /// Nominator nomination amount is less tha `StakingMinNomination`. - NominationBelowMin, - /// Validator account exit pool request is in progress. - AlreadyLeaving, - /// Cannot activate, validator account exit pool request is in progress. - CannotActivateIfLeaving, - /// Validator is already nominated by `MaxNominatorsPerValidator` nominators. - TooManyNominators, - /// Nominator already nominated `MaxValidatorPerNominator` validators. - ExceedMaxValidatorPerNom, - /// Trying for duplicate nomination. - AlreadyNominatedValidator, - /// Selected validator is not nominated by nominator. - NominationDNE, - /// Underflow in bonded value. - Underflow, - /// Selected number of validators per session is below `MinSelectedValidators`. - CannotSetBelowMin, - /// Invalid argument to `slash_cancel_deferred()`, arg `controllers` is empty. - EmptyTargets, - /// Invalid argument to `slash_cancel_deferred()`, arg `session_idx` is invalid. - InvalidSessionIndex, - /// Unbonding request exceeds `MaxChunkUnlock`. - NoMoreChunks, - /// Error in Increment the reference counter on an account. - BadState, - /// Error Invalid arguments - InvalidArguments, } #[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 @@ -1087,12 +786,63 @@ pub mod pallet { Withdrawn(T::AccountId, BalanceOf), } - /// 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>; + #[pallet::error] + pub enum Error { + /// Not a validator account. + ValidatorDNE, + /// Not a nominator account. + NominatorDNE, + /// Validator pool full. + ValidatorPoolFull, + /// Validator account already part of validator pool. + ValidatorExists, + /// Nominator account already part of nominator pool. + NominatorExists, + /// Low free balance in caller account. + InsufficientBalance, + /// Rewards don't exist. + RewardsDNE, + /// Validator bond is less than `MinValidatorPoolStake` value. + ValidatorBondBelowMin, + /// Nominator bond is less than `MinNominatorStake` value. + NominatorBondBelowMin, + /// Nominator nomination amount is less tha `StakingMinNomination`. + NominationBelowMin, + /// Validator account exit pool request is in progress. + AlreadyLeaving, + /// Cannot activate, validator account exit pool request is in progress. + CannotActivateIfLeaving, + /// Validator is already nominated by `MaxNominatorsPerValidator` nominators. + TooManyNominators, + /// Nominator already nominated `MaxValidatorPerNominator` validators. + ExceedMaxValidatorPerNom, + /// Trying for duplicate nomination. + AlreadyNominatedValidator, + /// Selected validator is not nominated by nominator. + NominationDNE, + /// Underflow in bonded value. + Underflow, + /// Selected number of validators per session is below `MinSelectedValidators`. + CannotSetBelowMin, + /// Invalid argument to `slash_cancel_deferred()`, arg `controllers` is empty. + EmptyTargets, + /// Invalid argument to `slash_cancel_deferred()`, arg `session_idx` is invalid. + InvalidSessionIndex, + /// Unbonding request exceeds `MaxChunkUnlock`. + NoMoreChunks, + /// Error in Increment the reference counter on an account. + BadState, + /// Error Invalid arguments + InvalidArguments, + /// Unlock Request OverFlowError + UnlockOverFlowError, + /// Unable to retrive content from OrderSet. + OrderedSetFailure, + /// Nomination Overflow. + NominationOverflow, + /// Nomination Invalid + InvalidNomination, + } /// Maximum Validators allowed to join the validators pool #[pallet::storage] @@ -1134,38 +884,11 @@ 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<_, Vec, 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>>, ValueQuery>; - /// A queue of validators awaiting exit `BondedDuration` delay after request #[pallet::storage] #[pallet::getter(fn exit_queue)] - pub(crate) type ExitQueue = StorageValue<_, OrderedSet>, ValueQuery>; + pub(crate) type ExitQueue = + StorageValue<_, OrderedSet, T::DefaultStakingMaxValidators>, ValueQuery>; /// Snapshot of validator nomination stake at the start of the round #[pallet::storage] @@ -1176,7 +899,7 @@ pub mod pallet { SessionIndex, Twox64Concat, T::AccountId, - ValidatorSnapshot>, + ValidatorSnapshot, ValueQuery, >; @@ -1212,7 +935,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn stake_rewards)] pub(crate) type StakeRewards = - StorageMap<_, Twox64Concat, T::AccountId, Vec>>, ValueQuery>; + StorageMap<_, Twox64Concat, T::AccountId, BoundedVec>, T::MaxChunkUnlock>, ValueQuery>; /// The percentage of the slash that is distributed to reporters. /// @@ -1221,174 +944,44 @@ pub mod pallet { #[pallet::getter(fn slash_reward_proportion)] pub(crate) type SlashRewardProportion = StorageValue<_, Perbill, ValueQuery>; - /// Snapshot of validator slash state - #[pallet::storage] - #[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. + /// Get validator state associated with an account if account is collating else None #[pallet::storage] - #[pallet::getter(fn validator_slash_in_session)] - pub(crate) type ValidatorSlashInSession = StorageDoubleMap< + #[pallet::getter(fn validator_state)] + pub(crate) type ValidatorState = StorageMap< _, Twox64Concat, - SessionIndex, - Twox64Concat, T::AccountId, - (Perbill, BalanceOf), + Validator, OptionQuery, >; - /// All slashing events on nominators, - /// mapped by session to the highest slash value of the session. + /// Get nominator state associated with an account if account is nominating else None #[pallet::storage] - #[pallet::getter(fn nominator_slash_in_session)] - pub(crate) type NominatorSlashInSession = - StorageDoubleMap<_, Twox64Concat, SessionIndex, Twox64Concat, T::AccountId, BalanceOf, OptionQuery>; + #[pallet::getter(fn nominator_state)] + pub(crate) type NominatorState = StorageMap< + _, + Twox64Concat, + T::AccountId, + Nominator, + OptionQuery, + >; - /// All unapplied slashes that are queued for later. + /// The pool of validator validators, each with their total backing stake #[pallet::storage] - #[pallet::getter(fn unapplied_slashes)] - pub(crate) type UnappliedSlashes = - StorageMap<_, Twox64Concat, SessionIndex, Vec>>, ValueQuery>; + #[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<_, Vec, 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: vec![], - 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." - ); - >::put(&self.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) = >::select_session_validators(genesis_session_idx); - // 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, - )); - } - } - - #[cfg(feature = "std")] - impl GenesisConfig { - /// Direct implementation of `GenesisBuild::build_storage`. - /// - /// Kept in order not to break dependency. - pub fn build_storage(&self) -> Result { - >::build_storage(self) - } - - /// Direct implementation of `GenesisBuild::assimilate_storage`. - /// - /// Kept in order not to break dependency. - pub fn assimilate_storage(&self, storage: &mut sp_runtime::Storage) -> Result<(), String> { - >::assimilate_storage(self, storage) - } - } + pub(crate) type BondedSessions = + StorageValue<_, BoundedVec, ValueQuery>; 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() } @@ -1408,63 +1001,6 @@ pub mod pallet { }); }); } - // ensure validator is active before calling - pub fn remove_from_validators_pool(validator: T::AccountId) { - log::trace!("remove_from_validators_pool:[{:#?}] | Own[{:#?}]", line!(), validator); - >::mutate(|validators| { - validators.remove(&Bond::from_owner(validator.clone())); - }); - } - pub(crate) fn validator_deactivate(controller: &T::AccountId) { - log::trace!("validator_deactivate:[{:#?}] - Acc[{:#?}]", line!(), controller); - >::mutate(&controller, |maybe_validator| { - if let Some(valid_state) = maybe_validator { - valid_state.go_offline(); - Self::remove_from_validators_pool(controller.clone()); - } - }); - } - 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 noms = state - .clone() - .nominators - .0 - .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::from(noms); - - 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); - } - Self::deposit_event(Event::NominatorLeftValidator( - nominator, - validator, - nominator_stake, - state.total, - )); - Ok(().into()) - }, - )?; - Ok(().into()) - } - fn nominator_revokes_validator( acc: T::AccountId, validator: T::AccountId, @@ -1473,14 +1009,13 @@ pub mod pallet { let mut nominator_state = >::get(&acc).ok_or(>::NominatorDNE)?; ensure!( - nominator_state.unlocking.len() < T::MaxChunkUnlock::get(), + (nominator_state.unlocking.len() as u32) < T::MaxChunkUnlock::get(), >::NoMoreChunks, ); let old_active_bond = nominator_state.active_bond; - let remaining = nominator_state - .rm_nomination(validator.clone(), false) - .ok_or(>::NominationDNE)?; + + let remaining = nominator_state.rm_nomination(validator.clone(), false)?; if !do_force { ensure!( @@ -1493,357 +1028,60 @@ pub mod pallet { >::mutate(|x| *x = x.saturating_sub(old_active_bond.saturating_sub(remaining))); - nominator_state.unlocking.push(UnlockChunk { - value: old_active_bond.saturating_sub(nominator_state.active_bond), - session_idx: Self::active_session().saturating_add(T::BondedDuration::get()), - }); + nominator_state + .unlocking + .try_push(UnlockChunk { + value: old_active_bond.saturating_sub(nominator_state.active_bond), + session_idx: Self::active_session().saturating_add(T::BondedDuration::get()), + }) + .map_err(|_| >::UnlockOverFlowError)?; >::insert(acc, nominator_state); 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; - fn validator_revokes_nomination(nominator_acc: T::AccountId, validator: T::AccountId) { - >::mutate(&nominator_acc, |maybe_nominator_state| { - if let Some(nominator_state) = maybe_nominator_state.as_mut() { - let old_active_bond = nominator_state.active_bond; - - if let Some(_remaining) = nominator_state.rm_nomination(validator.clone(), false) { - nominator_state.unlocking.push(UnlockChunk { - value: old_active_bond.saturating_sub(nominator_state.active_bond), - session_idx: Self::active_session(), - }); - - Self::deposit_event(Event::NominatorLeftValidator( - nominator_acc.clone(), - validator, - old_active_bond.saturating_sub(nominator_state.active_bond), - Zero::zero(), - )); - } - } - }); - } - - fn validator_freeze_nomination(nominator_acc: T::AccountId, validator: T::AccountId) { - >::mutate(&nominator_acc, |maybe_nominator_state| { - if let Some(nominator_state) = maybe_nominator_state.as_mut() { - let old_active_bond = nominator_state.active_bond; - - if let Some(_remaining) = nominator_state.rm_nomination(validator.clone(), true) { - Self::deposit_event(Event::NominationBelowThreashold( - nominator_acc.clone(), - validator, - old_active_bond.saturating_sub(nominator_state.active_bond), - nominator_state.frozen_bond, - nominator_state.active_bond, - )); - } - } - }); - } - - pub(crate) fn pay_stakers(next: SessionIndex) { - log::trace!("pay_stakers:[{:#?}] - Sess-idx[{:#?}]", line!(), next); - - let mint = |amt: BalanceOf, to: T::AccountId| { - if amt > T::Currency::minimum_balance() { - >::mutate(&to, |rewards| { - rewards.push(StakeReward { - session_idx: next, - value: amt, - }); - // *rewards = rewards; - Self::deposit_event(Event::StakeReward(to.clone(), amt)); - }); - } - }; - - 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 - mint(amt_due, val.clone()); - log::trace!("pay_stakers:[{:#?}] - L3 Solo Mode", line!()); - } 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, - ); - - mint(val_due, val.clone()); - // pay nominators due portion - for Bond { owner, amount } in state.nominators { - let percent = Perbill::from_rational(amount, state.total); - let due = percent * amt_due; - mint(due, owner); - } - } - } - } - 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 - for bond in state.nominators.0 { - Self::validator_revokes_nomination(bond.owner.clone(), x.owner.clone()); - } - // return stake to validator - let mut unlock_chunk_total: BalanceOf = Zero::zero(); - let _ = state.unlocking.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::>>(); - >::put(OrderedSet::from(remain_exits)); - } - - fn active_stake_reconciliation() { - let reconciled_list = >::get() - .0 - .into_iter() - .filter_map(|x| { - Self::validator_stake_reconciliation(&x.owner); - 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, - amount: valid_state.total, - }) - } else { - None - } - } else { - None - } - }) - .collect::>>>(); - >::put(OrderedSet::from(reconciled_list)); - } - - pub(crate) fn validator_stake_reconciliation(controller: &T::AccountId) { - >::mutate(&controller, |maybe_validator| { - if let Some(valid_state) = maybe_validator { - let noms = valid_state - .clone() + let nominations_inner: Vec>> = state .nominators - .0 + .get_inner() + .map_err(|_| >::OrderedSetFailure)?; + + let noms: Vec>> = nominations_inner .into_iter() .filter_map(|nom| { - if nom.amount < Self::staking_min_nomination_chill_threshold() { - Self::validator_freeze_nomination(nom.owner.clone(), controller.clone()); - valid_state.dec_nominator(nom.owner.clone(), nom.amount); - None - } else { + 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)?; - let nominators = OrderedSet::from(noms); - 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, - )); + 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); } - } - }); - } - - /// Best as in most cumulatively supported in terms of stake - pub(crate) fn select_session_validators(next: SessionIndex) -> (u32, BalanceOf) { - let (mut validators_count, mut total) = (0u32, >::zero()); - let mut validators = >::get().0; - // 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().is_empty() { - top_validators = Self::invulnerables() - .iter() - .chain(top_validators.iter()) - .cloned() - .collect::>(); - } - - top_validators.sort(); - top_validators.dedup(); - - // 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.into(); - >::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(top_validators); - (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 unapplied_slash in session_slashes { - slashing::apply_slash::(unapplied_slash); - } - } + Self::deposit_event(Event::NominatorLeftValidator( + nominator, + validator, + nominator_stake, + state.total, + )); + Ok(().into()) + }, + )?; + Ok(().into()) } } } diff --git a/pallets/staking/src/set.rs b/pallets/staking/src/set.rs index 384ffd962bf..771cbb0a00d 100644 --- a/pallets/staking/src/set.rs +++ b/pallets/staking/src/set.rs @@ -15,85 +15,212 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -use codec::{Decode, Encode}; -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; -use sp_runtime::RuntimeDebug; -use sp_std::prelude::*; - -/// An ordered set backed by `Vec` -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(RuntimeDebug, PartialEq, Eq, Encode, Decode, Clone, scale_info::TypeInfo)] -pub struct OrderedSet(pub Vec); - -impl Default for OrderedSet { +use codec::{Codec, Decode, Encode}; +use derivative::Derivative; +use frame_support::{bounded_vec, pallet_prelude::MaxEncodedLen, traits::Get, BoundedVec}; +use scale_info::TypeInfo; +use sp_std::{cmp::Ordering, fmt::Debug, marker::PhantomData, 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"), + Clone(bound = "T: Clone"), + PartialEq(bound = "T: PartialEq"), + Eq(bound = "T: Eq") +)] +#[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>); + +impl MaxEncodedLen for OrderedSet +where + S: Get, +{ + fn max_encoded_len() -> usize { + S::get() as usize + } +} + +impl Default for OrderedSet +where + T: Encode + Decode, + S: Get, +{ /// Create a default empty set fn default() -> Self { - Self(Vec::new()) + let inner: Vec = Vec::with_capacity(S::get() as usize); + Self(BoundedCodecWrapper::try_from(inner).expect("OrderedSet Failed To Create Default")) } } -impl OrderedSet { +impl OrderedSet +where + T: Ord + Encode + Decode + Clone, + S: Get, +{ /// Create a new empty set pub fn new() -> Self { - Self(Vec::new()) + Self::default() } /// Create a set from a `Vec`. /// `v` will be sorted and dedup first. - pub fn from(mut v: Vec) -> Self { - v.sort(); - v.dedup(); - Self::from_sorted_set(v) + pub fn from(mut inner: Vec) -> Result { + inner.sort(); + inner.dedup(); + Self::from_sorted_set(inner) } /// Create a set from a `Vec`. /// Assume `v` is sorted and contain unique elements. - pub fn from_sorted_set(v: Vec) -> Self { - Self(v) + pub fn from_sorted_set(val: Vec) -> Result { + let inner = BoundedCodecWrapper::try_from(val).map_err(|_| { + log::error!("OrderedSet Failed To Create From Vec"); + () + })?; + Ok(Self(inner)) + } + + pub fn get_inner(&self) -> Result, ()> { + let inner = self.0.clone().try_into_inner().map_err(|_| ())?; + Ok(inner.clone()) + } + + pub fn len(&self) -> Result { + let inner = self.0.clone().try_into_inner().map_err(|_| ())?; + Ok(inner.len()) + } + + pub fn update_inner(&mut self, inner: Vec) -> Result<(), ()> { + self.0 = BoundedCodecWrapper::try_from(inner).map_err(|_| { + log::error!("Orderset Failed to Update inner"); + () + })?; + Ok(()) } /// Insert an element. /// Return true if insertion happened. - pub fn insert(&mut self, value: T) -> bool { - match self.0.binary_search(&value) { - Ok(_) => false, + 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"); + () + })?; + + match inner.binary_search(&value) { + Ok(_) => Ok(false), Err(loc) => { - self.0.insert(loc, value); - true + inner.insert(loc, value); + self.0 = BoundedCodecWrapper::try_from(inner).map_err(|_| { + log::error!("Orderset Failed to Insert"); + () + })?; + Ok(true) } } } /// Remove an element. /// Return true if removal happened. - pub fn remove(&mut self, value: &T) -> bool { - match self.0.binary_search(value) { + 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"); + () + })?; + + match inner.binary_search(&value) { Ok(loc) => { - self.0.remove(loc); - true + inner.remove(loc); + self.0 = BoundedCodecWrapper::try_from(inner).map_err(|_| { + log::error!("Orderset Failed to Remove"); + () + })?; + Ok(true) } - Err(_) => false, + Err(_) => Ok(false), } } /// Return if the set contains `value` - pub fn contains(&self, value: &T) -> Option { - // self.0.binary_search(&value).is_ok() - match self.0.binary_search(value) { - Ok(loc) => Some(loc), - Err(_) => None, + pub fn contains(&self, value: &T) -> Result { + let inner = self.0.clone().try_into_inner().map_err(|_| { + log::error!("Orderset Failed to Decode"); + () + })?; + + match inner.binary_search(&value) { + Ok(loc) => Ok(loc), + Err(_) => Err(()), } } /// Clear the set - pub fn clear(&mut self) { - self.0.clear(); + pub fn clear(&mut self) -> Result<(), ()> { + let mut inner = self.0.clone().try_into_inner().map_err(|_| { + log::error!("Orderset Failed to Decode"); + () + })?; + + inner.clear(); + self.0 = BoundedCodecWrapper::try_from(inner).map_err(|_| { + log::error!("Orderset Failed to Remove"); + () + })?; + + Ok(()) } } -impl From> for OrderedSet { - fn from(v: Vec) -> Self { - Self::from(v) +impl TryFrom> for OrderedSet +where + T: Ord + Encode + Decode + Clone, + S: Get, +{ + type Error = (); + fn try_from(v: Vec) -> Result { + let rval = Self::from(v).map_err(|_| ())?; + Ok(rval) } } diff --git a/pallets/staking/src/types.rs b/pallets/staking/src/types.rs index ab4e59e01ad..e9ef1869feb 100644 --- a/pallets/staking/src/types.rs +++ b/pallets/staking/src/types.rs @@ -16,15 +16,24 @@ * along with this program. If not, see . */ -use super::{ActiveSession, AtStake, BalanceOf, Config, Pallet}; +use super::{ActiveSession, AtStake, BalanceOf, Config, Error, Pallet}; use crate::set::OrderedSet; -use codec::{Decode, Encode, HasCompact}; +use codec::{Codec, Decode, Encode, HasCompact}; +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}, RuntimeDebug, }; use sp_staking::SessionIndex; -use sp_std::{cmp::Ordering, convert::From, prelude::*}; +use sp_std::marker::PhantomData; +use sp_std::{ + cmp::{max, Ordering}, + convert::From, + fmt::Debug, + prelude::*, +}; /// The index of a slashing span - unique to each controller. pub(crate) type SpanIndex = u32; @@ -32,43 +41,55 @@ pub(crate) type SpanIndex = u32; /// The type define for validators reward pub(crate) type RewardPoint = u32; -#[derive(Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)] +#[derive(Clone, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub struct Bond { pub owner: AccountId, pub amount: Balance, } -impl Bond { - pub(crate) fn from_owner(owner: A) -> Self { +impl Bond +where + Balance: Default, +{ + pub(crate) fn from_owner(owner: AccountId) -> Self { Bond { owner, - amount: B::default(), + amount: Balance::default(), } } } -impl Eq for Bond {} +impl Eq for Bond where AccountId: Ord {} -impl PartialEq for Bond { +impl PartialEq for Bond +where + AccountId: Ord, +{ fn eq(&self, other: &Self) -> bool { self.owner == other.owner } } -impl Ord for Bond { +impl Ord for Bond +where + AccountId: Ord, +{ fn cmp(&self, other: &Self) -> Ordering { self.owner.cmp(&other.owner) } } -impl PartialOrd for Bond { +impl PartialOrd for Bond +where + AccountId: Ord, +{ fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } /// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be unlocked. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Copy, scale_info::TypeInfo)] +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Copy, MaxEncodedLen, TypeInfo)] pub struct UnlockChunk { /// Amount of funds to be unlocked. pub(crate) value: Balance, @@ -78,7 +99,7 @@ pub struct UnlockChunk { pub(crate) type StakeReward = UnlockChunk; -#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)] +#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] /// The activity status of the validator pub enum ValidatorStatus { /// Committed to be online and producing valid blocks @@ -95,47 +116,67 @@ impl Default for ValidatorStatus { } } -#[derive(Encode, Decode, Clone, RuntimeDebug, scale_info::TypeInfo)] /// Global validator state with commission fee, bonded stake, and nominations -pub struct Validator { - pub id: AccountId, - pub bond: Balance, - pub nomi_bond_total: Balance, - pub nominators: OrderedSet>, - pub total: Balance, +#[derive(Derivative)] +#[derivative(Debug(bound = "T: Debug"), Clone(bound = "T: Clone"))] +#[derive(Decode, Encode, TypeInfo)] +#[scale_info(skip_type_params(T, MaxNominators, MaxUnlock))] +pub struct Validator, MaxUnlock: Get> { + pub id: ::AccountId, + pub bond: BalanceOf, + pub nomi_bond_total: BalanceOf, + pub nominators: OrderedSet::AccountId, BalanceOf>, MaxNominators>, + pub total: BalanceOf, pub state: ValidatorStatus, - pub unlocking: Vec>, + pub unlocking: BoundedVec>, MaxUnlock>, } -impl< - A: Ord + Clone + sp_std::fmt::Debug, - B: AtLeast32BitUnsigned + Ord + Copy + sp_std::ops::AddAssign + sp_std::ops::SubAssign + Default, - > Validator +impl MaxEncodedLen for Validator +where + T: Config, + MaxNominators: Get, + MaxUnlock: Get, { - pub fn new(id: A, bond: B) -> Self { + fn max_encoded_len() -> usize { + max(MaxNominators::get(), MaxUnlock::get()) as usize + } +} + +impl Validator +where + T: Config, + MaxNominators: Get, + MaxUnlock: Get, +{ + pub fn new(id: ::AccountId, bond: BalanceOf) -> Self { let total = bond; + let unlocking: BoundedVec>, MaxUnlock> = bounded_vec![]; Validator { id, bond, nomi_bond_total: Zero::zero(), - nominators: OrderedSet::new(), + nominators: OrderedSet::::AccountId, BalanceOf>, MaxNominators>::new(), total, state: ValidatorStatus::default(), // default active - unlocking: Vec::new(), + unlocking: unlocking, } } + pub fn is_active(&self) -> bool { self.state == ValidatorStatus::Active } + pub fn is_leaving(&self) -> bool { matches!(self.state, ValidatorStatus::Leaving(_)) } - pub fn bond_more(&mut self, more: B) { + + pub fn bond_more(&mut self, more: BalanceOf) { self.bond = self.bond.saturating_add(more); self.total = self.total.saturating_add(more); } + // Returns None if underflow or less == self.bond (in which case validator should leave) - pub fn bond_less(&mut self, less: B) -> Option { + pub fn bond_less(&mut self, less: BalanceOf) -> Option> { if self.bond > less { self.bond = self.bond.saturating_sub(less); Some(self.bond) @@ -143,145 +184,153 @@ impl< None } } - pub fn inc_nominator(&mut self, nominator: A, more: B) { - if let Ok(loc) = self.nominators.0.binary_search(&Bond::from_owner(nominator)) { - let nom_bond = &mut self.nominators.0[loc]; + + pub fn inc_nominator( + &mut self, + nominator: ::AccountId, + more: BalanceOf, + ) -> Result<(), Error> { + let mut nominators: Vec::AccountId, BalanceOf>> = + self.nominators.get_inner().map_err(|_| >::OrderedSetFailure)?; + + if let Ok(loc) = nominators.binary_search(&Bond::from_owner(nominator)) { + let nom_bond = &mut nominators[loc]; nom_bond.amount = nom_bond.amount.saturating_add(more); + self.nominators + .update_inner(nominators) + .map_err(|_| >::NominationOverflow)?; self.nomi_bond_total = self.nomi_bond_total.saturating_add(more); self.total = self.total.saturating_add(more); - }; + } else { + return Err(>::InvalidNomination); + } + + Ok(()) } - pub fn dec_nominator(&mut self, nominator: A, less: B) { - if let Ok(loc) = self.nominators.0.binary_search(&Bond::from_owner(nominator)) { - let nom_bond = &mut self.nominators.0[loc]; + + pub fn dec_nominator( + &mut self, + nominator: ::AccountId, + less: BalanceOf, + ) -> Result<(), Error> { + let mut nominators: Vec::AccountId, BalanceOf>> = + self.nominators.get_inner().map_err(|_| >::OrderedSetFailure)?; + + if let Ok(loc) = nominators.binary_search(&Bond::from_owner(nominator)) { + 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); self.total = self.total.saturating_sub(less); - }; + } else { + return Err(>::InvalidNomination); + } + Ok(()) } + pub fn go_offline(&mut self) { self.state = ValidatorStatus::Idle; } + pub fn go_online(&mut self) { self.state = ValidatorStatus::Active; } + pub fn leave_validators_pool(&mut self, round: SessionIndex) { self.state = ValidatorStatus::Leaving(round); } -} - -impl Validator -where - Balance: AtLeast32BitUnsigned + Saturating + Copy + sp_std::ops::AddAssign + sp_std::ops::SubAssign, -{ - /// 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: Balance, minimum_balance: Balance) -> Balance { - let pre_total = self.total; - let total = &mut self.total; - let active = &mut self.bond; - - let slash_out_of = |total_remaining: &mut Balance, target: &mut Balance, value: &mut Balance| { - 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); + pub fn build_snapshot(&self) -> Result, Error> { + let nominators: Vec::AccountId, BalanceOf>> = + self.nominators.get_inner().map_err(|_| >::OrderedSetFailure)?; - 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(); + // let nominators_snapshot = BoundedVec::try_from(nominators).map_err(|_| + // >::OrderedSetFailure)?; - // 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) -> Balance { - 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 + Ok(ValidatorSnapshot { + bond: self.bond, + nominators: nominators, + total: self.bond + self.nomi_bond_total, + stub: >::default(), + }) } } -#[derive(Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)] /// Snapshot of validator state at the start of the round for which they are selected -pub struct ValidatorSnapshot { - pub bond: Balance, - pub nominators: Vec>, - pub total: Balance, +#[derive(Derivative)] +#[derivative(Debug(bound = "T: Debug"), Clone(bound = "T: Clone"))] +#[derive(Decode, Encode, TypeInfo)] +#[scale_info(skip_type_params(T, MaxNominators))] +pub struct ValidatorSnapshot> { + pub bond: BalanceOf, + pub nominators: Vec::AccountId, BalanceOf>>, + pub total: BalanceOf, + pub stub: PhantomData, } -impl + sp_std::ops::SubAssign> - From> for ValidatorSnapshot +impl MaxEncodedLen for ValidatorSnapshot +where + T: Config, + MaxNominators: Get, { - fn from(other: Validator) -> ValidatorSnapshot { - ValidatorSnapshot { - bond: other.bond, - nominators: other.nominators.0, - total: other.bond + other.nomi_bond_total, - } + fn max_encoded_len() -> usize { + MaxNominators::get() as usize } } -impl Default for ValidatorSnapshot { +impl Default for ValidatorSnapshot +where + T: Config, + MaxNominators: Get, +{ fn default() -> Self { + let inner: Vec::AccountId, BalanceOf>> = + Vec::with_capacity(MaxNominators::get() as usize); + + // let nominators: BoundedVec::AccountId, BalanceOf>, + // MaxNominators> = BoundedVec::try_from(inner).expect("ValidatorSnapshot Failed To Create + // Default"); + Self { - bond: Default::default(), - nominators: vec![], - total: Default::default(), + bond: >::default(), + nominators: inner, + total: >::default(), + stub: >::default(), } } } -impl Eq for ValidatorSnapshot {} +impl Eq for ValidatorSnapshot +where + T: Config, + MaxNominators: Get, +{ +} -impl PartialEq for ValidatorSnapshot { +impl PartialEq for ValidatorSnapshot +where + T: Config, + MaxNominators: Get, +{ fn eq(&self, other: &Self) -> bool { self.nominators == other.nominators } } -impl Ord for ValidatorSnapshot { +impl Ord for ValidatorSnapshot +where + T: Config, + MaxNominators: Get, +{ fn cmp(&self, other: &Self) -> Ordering { self.total.cmp(&other.total) } } -impl PartialOrd for ValidatorSnapshot { +impl PartialOrd for ValidatorSnapshot +where + T: Config, + MaxNominators: Get, +{ fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) // Some(self.total.cmp(&other.total)) @@ -293,12 +342,13 @@ impl PartialOrd for ValidatorSnapshot(sp_std::marker::PhantomData); +pub struct ValidatorSnapshotOf>(PhantomData, PhantomData); -impl Convert>>> - for ValidatorSnapshotOf +impl> + Convert>> + for ValidatorSnapshotOf { - fn convert(validator: T::AccountId) -> Option>> { + fn convert(validator: T::AccountId) -> Option> { let now = >::get(); if >::contains_key(now, &validator) { Some(>::at_stake(now, &validator)) @@ -308,59 +358,88 @@ impl Convert { - pub nominations: OrderedSet>, - pub total: Balance, - pub active_bond: Balance, - pub frozen_bond: Balance, - pub unlocking: Vec>, +#[derive(Derivative)] +#[derivative(Debug(bound = "T: Debug"), Clone(bound = "T: Clone"))] +#[derive(Decode, Encode, TypeInfo)] +#[scale_info(skip_type_params(T, MaxNomination, MaxUnlock))] +pub struct Nominator, MaxUnlock: Get> { + pub nominations: OrderedSet::AccountId, BalanceOf>, MaxNomination>, + pub total: BalanceOf, + pub active_bond: BalanceOf, + pub frozen_bond: BalanceOf, + pub unlocking: BoundedVec>, MaxUnlock>, +} + +impl MaxEncodedLen for Nominator +where + T: Config, + MaxNomination: Get, + MaxUnlock: Get, +{ + fn max_encoded_len() -> usize { + max(MaxNomination::get(), MaxUnlock::get()) as usize + } } -impl< - AccountId: Ord + Clone, - Balance: Copy - + AtLeast32BitUnsigned - + Saturating - + sp_std::ops::AddAssign - + sp_std::ops::Add - + sp_std::ops::SubAssign - + PartialOrd - + Default, - > Nominator +impl Nominator +where + T: Config, + MaxNomination: Get, + MaxUnlock: Get, { - pub fn new(validator: AccountId, amount: Balance) -> Self { - Nominator { - nominations: OrderedSet::from(vec![Bond { - owner: validator, - amount, - }]), + pub fn new(validator: ::AccountId, amount: BalanceOf) -> Result> { + let unlocking: BoundedVec>, MaxUnlock> = bounded_vec![]; + + let nominations = OrderedSet::try_from(vec![Bond { + owner: validator, + amount, + }]) + .map_err(|_| >::OrderedSetFailure)?; + + Ok(Nominator { + nominations: nominations, total: amount, active_bond: amount, frozen_bond: Zero::zero(), - unlocking: Vec::new(), - } + unlocking: unlocking, + }) } - pub fn add_nomination(&mut self, bond: Bond, unfreeze_bond: bool) -> bool { + pub fn add_nomination( + &mut self, + bond: Bond<::AccountId, BalanceOf>, + unfreeze_bond: bool, + ) -> Result> { let amt = bond.amount; - if self.nominations.insert(bond) { + let status = self + .nominations + .insert(bond) + .map_err(|_| >::NominationOverflow)?; + if status { if unfreeze_bond { self.frozen_bond = Zero::zero(); } self.total = self.total.saturating_add(amt); self.active_bond = self.active_bond.saturating_add(amt); - true + Ok(true) } else { - false + return Err(>::InvalidNomination); } } // 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) -> Option { - let mut amt: Option = None; - let nominations = self + pub fn rm_nomination( + &mut self, + validator: ::AccountId, + freeze_bond: bool, + ) -> Result, Error> { + let mut amt: Option> = None; + + let nominations_inner = self .nominations - .0 + .get_inner() + .map_err(|_| >::OrderedSetFailure)?; + + let nominations: Vec::AccountId, BalanceOf>> = nominations_inner .iter() .filter_map(|x| { if x.owner == validator { @@ -372,18 +451,17 @@ impl< }) .collect(); if let Some(balance) = amt { - self.nominations = OrderedSet::from(nominations); + self.nominations = OrderedSet::try_from(nominations).map_err(|_| >::OrderedSetFailure)?; self.active_bond = self.active_bond.saturating_sub(balance); if freeze_bond { self.frozen_bond = self.frozen_bond.saturating_add(balance); } - Some(self.active_bond) + Ok(self.active_bond) } else { - None + return Err(>::NominationDNE); } } - - pub fn unbond_frozen(&mut self) -> Option { + pub fn unbond_frozen(&mut self) -> Option> { if self.frozen_bond > Zero::zero() { let frozen_bond = self.frozen_bond; self.total = self.total.saturating_sub(frozen_bond); @@ -393,144 +471,56 @@ impl< None } } - // Returns None if nomination not found - pub fn inc_nomination(&mut self, validator: AccountId, more: Balance, unfreeze_bond: bool) -> Option { - match self.nominations.0.binary_search(&Bond::from_owner(validator)) { - Ok(loc) => { - let nom_bond = &mut self.nominations.0[loc]; - nom_bond.amount = nom_bond.amount.saturating_add(more); - self.total = self.total.saturating_add(more); - self.active_bond = self.active_bond.saturating_add(more); - if unfreeze_bond { - self.frozen_bond = Zero::zero(); - } - Some(nom_bond.amount) - } - Err(_) => None, - } - } - pub fn dec_nomination(&mut self, validator: AccountId, less: Balance) -> Result { - match self.nominations.0.binary_search(&Bond::from_owner(validator)) { - Ok(loc) => { - let nom_bond = &mut self.nominations.0[loc]; - if nom_bond.amount > less { - nom_bond.amount = nom_bond.amount.saturating_sub(less); - self.active_bond = self.active_bond.saturating_sub(less); - Ok(nom_bond.amount) - } else { - Err("Underflow") - } - } - Err(_) => Err("NominationDNE"), - } - } -} - -impl Nominator -where - AccountId: Ord + Clone, - Balance: AtLeast32BitUnsigned + Saturating + Copy + sp_std::ops::AddAssign + sp_std::ops::SubAssign + Default, -{ - /// 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( + pub fn inc_nomination( &mut self, - validator: AccountId, - mut value: Balance, - minimum_balance: Balance, - ) -> Balance { - 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 Balance, target: &mut Balance, value: &mut Balance| { - 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)) { - let nom_bond = &mut self.nominations.0[loc]; - slash_out_of(active_bond, &mut nom_bond.amount, &mut value); - }; + validator: ::AccountId, + more: BalanceOf, + unfreeze_bond: bool, + ) -> Result, Error> { + let mut nominations_inner = self + .nominations + .get_inner() + .map_err(|_| >::OrderedSetFailure)?; - *total = total.saturating_sub(pre_active_bond.saturating_sub(*active_bond)); + if let Ok(loc) = nominations_inner.binary_search(&Bond::from_owner(validator)) { + nominations_inner[loc].amount = nominations_inner[loc].amount.saturating_add(more); + let nom_bond_amount = nominations_inner[loc].amount; - 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) + self.nominations + .update_inner(nominations_inner) + .map_err(|_| >::NominationOverflow)?; + self.total = self.total.saturating_add(more); + self.active_bond = self.active_bond.saturating_add(more); + if unfreeze_bond { + self.frozen_bond = Zero::zero(); + } + Ok(nom_bond_amount) + } else { + return Err(>::NominationDNE); + } } - /// 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) -> Balance { - let mut total = self.total; - self.unlocking.retain(|&chunk| { - if chunk.session_idx > current_session { - true + pub fn dec_nomination( + &mut self, + validator: ::AccountId, + less: BalanceOf, + ) -> Result, Error> { + let mut nominations_inner = self + .nominations + .get_inner() + .map_err(|_| >::OrderedSetFailure)?; + + if let Ok(loc) = nominations_inner.binary_search(&Bond::from_owner(validator)) { + if nominations_inner[loc].amount > less { + nominations_inner[loc].amount = nominations_inner[loc].amount.saturating_sub(less); + let nom_bond_amount = nominations_inner[loc].amount; + self.active_bond = self.active_bond.saturating_sub(less); + Ok(nom_bond_amount) } else { - total = total.saturating_sub(chunk.value); - false + return Err(>::Underflow); } - }); - let unlocked_val = self.total.saturating_sub(total); - self.total = total; - unlocked_val - } -} - -/// A pending slash record. The value of the slash has been computed but not applied yet, -/// rather deferred for several eras. -#[derive(Encode, Decode, RuntimeDebug, Clone, scale_info::TypeInfo)] -pub struct UnappliedSlash { - /// The stash ID of the offending validator. - pub(crate) validator: AccountId, - /// The validator's own slash. - pub(crate) own: Balance, - /// All other slashed stakers and amounts. - pub(crate) others: Vec<(AccountId, Balance)>, - /// Reporters of the offence; bounty payout recipients. - pub(crate) reporters: Vec, - /// The amount of payout. - pub(crate) payout: Balance, -} - -#[allow(dead_code)] -impl UnappliedSlash { - pub(crate) fn from_default(validator: AccountId) -> Self { - Self { - validator, - own: Default::default(), - others: vec![], - reporters: vec![], - payout: Default::default(), + } else { + return Err(>::NominationDNE); } } }