// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Staking FRAME Pallet. use codec::Codec; use frame_election_provider_support::{ ElectionProvider, ElectionProviderBase, SortedListProvider, VoteWeight, }; use frame_support::{ pallet_prelude::*, traits::{ Currency, Defensive, DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get, InspectLockableCurrency, LockableCurrency, OnUnbalanced, UnixTime, WithdrawReasons, }, weights::Weight, BoundedVec, }; use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; use sp_runtime::{ traits::{SaturatedConversion, StaticLookup, Zero}, ArithmeticError, Perbill, Percent, }; use sp_staking::{ EraIndex, Page, SessionIndex, StakingAccount::{self, Controller, Stash}, }; use sp_std::prelude::*; mod impls; pub use impls::*; use crate::{ slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, DisablingStrategy, EraPayout, EraRewardPoints, Exposure, ExposurePage, Forcing, LedgerIntegrityState, MaxNominationsOf, NegativeImbalanceOf, Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, }; // The speculative number of spans are used as an input of the weight annotation of // [`Call::unbond`], as the post dispatch weight may depend on the number of slashing span on the // account which is not provided as an input. The value set should be conservative but sensible. pub(crate) const SPECULATIVE_NUM_SPANS: u32 = 32; #[frame_support::pallet] pub mod pallet { use frame_election_provider_support::ElectionDataProvider; use crate::{BenchmarkingConfig, PagedExposureMetadata}; use super::*; /// The in-code storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(15); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); /// Possible operations on the configuration values of this pallet. #[derive(TypeInfo, Debug, Clone, Encode, Decode, PartialEq)] pub enum ConfigOp { /// Don't change. Noop, /// Set the given value. Set(T), /// Remove from storage. Remove, } #[pallet::config] pub trait Config: frame_system::Config { /// The staking balance. type Currency: LockableCurrency< Self::AccountId, Moment = BlockNumberFor, Balance = Self::CurrencyBalance, > + InspectLockableCurrency; /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to /// `From`. type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned + codec::FullCodec + Copy + MaybeSerializeDeserialize + sp_std::fmt::Debug + Default + From + TypeInfo + MaxEncodedLen; /// Time used for computing era duration. /// /// It is guaranteed to start being called from the first `on_finalize`. Thus value at /// genesis is not used. type UnixTime: UnixTime; /// Convert a balance into a number used for election calculation. This must fit into a /// `u64` but is allowed to be sensibly lossy. The `u64` is used to communicate with the /// [`frame_election_provider_support`] crate which accepts u64 numbers and does operations /// in 128. /// Consequently, the backward convert is used convert the u128s from sp-elections back to a /// [`BalanceOf`]. type CurrencyToVote: sp_staking::currency_to_vote::CurrencyToVote>; /// Something that provides the election functionality. type ElectionProvider: ElectionProvider< AccountId = Self::AccountId, BlockNumber = BlockNumberFor, // we only accept an election provider that has staking as data provider. DataProvider = Pallet, >; /// Something that provides the election functionality at genesis. type GenesisElectionProvider: ElectionProvider< AccountId = Self::AccountId, BlockNumber = BlockNumberFor, DataProvider = Pallet, >; /// Something that defines the maximum number of nominations per nominator. type NominationsQuota: NominationsQuota>; /// Number of eras to keep in history. /// /// Following information is kept for eras in `[current_era - /// HistoryDepth, current_era]`: `ErasStakers`, `ErasStakersClipped`, /// `ErasValidatorPrefs`, `ErasValidatorReward`, `ErasRewardPoints`, /// `ErasTotalStake`, `ErasStartSessionIndex`, `ClaimedRewards`, `ErasStakersPaged`, /// `ErasStakersOverview`. /// /// Must be more than the number of eras delayed by session. /// I.e. active era must always be in history. I.e. `active_era > /// current_era - history_depth` must be guaranteed. /// /// If migrating an existing pallet from storage value to config value, /// this should be set to same value or greater as in storage. /// /// Note: `HistoryDepth` is used as the upper bound for the `BoundedVec` /// item `StakingLedger.legacy_claimed_rewards`. Setting this value lower than /// the existing value can lead to inconsistencies in the /// `StakingLedger` and will need to be handled properly in a migration. /// The test `reducing_history_depth_abrupt` shows this effect. #[pallet::constant] type HistoryDepth: Get; /// Tokens have been minted and are unused for validator-reward. /// See [Era payout](./index.html#era-payout). type RewardRemainder: OnUnbalanced>; /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Handler for the unbalanced reduction when slashing a staker. type Slash: OnUnbalanced>; /// Handler for the unbalanced increment when rewarding a staker. /// NOTE: in most cases, the implementation of `OnUnbalanced` should modify the total /// issuance. type Reward: OnUnbalanced>; /// Number of sessions per era. #[pallet::constant] type SessionsPerEra: Get; /// Number of eras that staked funds must remain bonded for. #[pallet::constant] type BondingDuration: Get; /// Number of eras that slashes are deferred by, after computation. /// /// This should be less than the bonding duration. Set to 0 if slashes /// should be applied immediately, without opportunity for intervention. #[pallet::constant] type SlashDeferDuration: Get; /// The origin which can manage less critical staking parameters that does not require root. /// /// Supported actions: (1) cancel deferred slash, (2) set minimum commission. type AdminOrigin: EnsureOrigin; /// Interface for interacting with a session pallet. type SessionInterface: SessionInterface; /// The payout for validators and the system for the current era. /// See [Era payout](./index.html#era-payout). type EraPayout: EraPayout>; /// Something that can estimate the next session change, accurately or as a best effort /// guess. type NextNewSession: EstimateNextNewSession>; /// The maximum size of each `T::ExposurePage`. /// /// An `ExposurePage` is weakly bounded to a maximum of `MaxExposurePageSize` /// nominators. /// /// For older non-paged exposure, a reward payout was restricted to the top /// `MaxExposurePageSize` nominators. This is to limit the i/o cost for the /// nominator payout. /// /// Note: `MaxExposurePageSize` is used to bound `ClaimedRewards` and is unsafe to reduce /// without handling it in a migration. #[pallet::constant] type MaxExposurePageSize: Get; /// Something that provides a best-effort sorted list of voters aka electing nominators, /// used for NPoS election. /// /// The changes to nominators are reported to this. Moreover, each validator's self-vote is /// also reported as one independent vote. /// /// To keep the load off the chain as much as possible, changes made to the staked amount /// via rewards and slashes are not reported and thus need to be manually fixed by the /// staker. In case of `bags-list`, this always means using `rebag` and `putInFrontOf`. /// /// Invariant: what comes out of this list will always be a nominator. type VoterList: SortedListProvider; /// WIP: This is a noop as of now, the actual business logic that's described below is going /// to be introduced in a follow-up PR. /// /// Something that provides a best-effort sorted list of targets aka electable validators, /// used for NPoS election. /// /// The changes to the approval stake of each validator are reported to this. This means any /// change to: /// 1. The stake of any validator or nominator. /// 2. The targets of any nominator /// 3. The role of any staker (e.g. validator -> chilled, nominator -> validator, etc) /// /// Unlike `VoterList`, the values in this list are always kept up to date with reward and /// slash as well, and thus represent the accurate approval stake of all account being /// nominated by nominators. /// /// Note that while at the time of nomination, all targets are checked to be real /// validators, they can chill at any point, and their approval stakes will still be /// recorded. This implies that what comes out of iterating this list MIGHT NOT BE AN ACTIVE /// VALIDATOR. type TargetList: SortedListProvider>; /// The maximum number of `unlocking` chunks a [`StakingLedger`] can /// have. Effectively determines how many unique eras a staker may be /// unbonding in. /// /// Note: `MaxUnlockingChunks` is used as the upper bound for the /// `BoundedVec` item `StakingLedger.unlocking`. Setting this value /// lower than the existing value can lead to inconsistencies in the /// `StakingLedger` and will need to be handled properly in a runtime /// migration. The test `reducing_max_unlocking_chunks_abrupt` shows /// this effect. #[pallet::constant] type MaxUnlockingChunks: Get; /// The maximum amount of controller accounts that can be deprecated in one call. type MaxControllersInDeprecationBatch: Get; /// Something that listens to staking updates and performs actions based on the data it /// receives. /// /// WARNING: this only reports slashing and withdraw events for the time being. type EventListeners: sp_staking::OnStakingUpdate>; // `DisablingStragegy` controls how validators are disabled type DisablingStrategy: DisablingStrategy; /// Some parameters of the benchmarking. type BenchmarkingConfig: BenchmarkingConfig; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } /// The ideal number of active validators. #[pallet::storage] #[pallet::getter(fn validator_count)] pub type ValidatorCount = StorageValue<_, u32, ValueQuery>; /// Minimum number of staking participants before emergency conditions are imposed. #[pallet::storage] #[pallet::getter(fn minimum_validator_count)] pub type MinimumValidatorCount = StorageValue<_, u32, ValueQuery>; /// 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)] #[pallet::unbounded] pub type Invulnerables = StorageValue<_, Vec, ValueQuery>; /// Map from all locked "stash" accounts to the controller account. /// /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. #[pallet::storage] pub type Bonded = StorageMap<_, Twox64Concat, T::AccountId, T::AccountId>; /// The minimum active bond to become and maintain the role of a nominator. #[pallet::storage] pub type MinNominatorBond = StorageValue<_, BalanceOf, ValueQuery>; /// The minimum active bond to become and maintain the role of a validator. #[pallet::storage] pub type MinValidatorBond = StorageValue<_, BalanceOf, ValueQuery>; /// The minimum active nominator stake of the last successful election. #[pallet::storage] pub type MinimumActiveStake = StorageValue<_, BalanceOf, ValueQuery>; /// The minimum amount of commission that validators can set. /// /// If set to `0`, no limit exists. #[pallet::storage] pub type MinCommission = StorageValue<_, Perbill, ValueQuery>; /// Map from all (unlocked) "controller" accounts to the info regarding the staking. /// /// Note: All the reads and mutations to this storage *MUST* be done through the methods exposed /// by [`StakingLedger`] to ensure data and lock consistency. #[pallet::storage] pub type Ledger = StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>; /// Where the reward payment should be made. Keyed by stash. /// /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. #[pallet::storage] pub type Payee = StorageMap<_, Twox64Concat, T::AccountId, RewardDestination, OptionQuery>; /// The map from (wannabe) validator stash key to the preferences of that validator. /// /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. #[pallet::storage] #[pallet::getter(fn validators)] pub type Validators = CountedStorageMap<_, Twox64Concat, T::AccountId, ValidatorPrefs, ValueQuery>; /// The maximum validator count before we stop allowing new validators to join. /// /// When this value is not set, no limits are enforced. #[pallet::storage] pub type MaxValidatorsCount = StorageValue<_, u32, OptionQuery>; /// The map from nominator stash key to their nomination preferences, namely the validators that /// they wish to support. /// /// Note that the keys of this storage map might become non-decodable in case the /// account's [`NominationsQuota::MaxNominations`] configuration is decreased. /// In this rare case, these nominators /// are still existent in storage, their key is correct and retrievable (i.e. `contains_key` /// indicates that they exist), but their value cannot be decoded. Therefore, the non-decodable /// nominators will effectively not-exist, until they re-submit their preferences such that it /// is within the bounds of the newly set `Config::MaxNominations`. /// /// This implies that `::iter_keys().count()` and `::iter().count()` might return different /// values for this map. Moreover, the main `::count()` is aligned with the former, namely the /// number of keys that exist. /// /// Lastly, if any of the nominators become non-decodable, they can be chilled immediately via /// [`Call::chill_other`] dispatchable by anyone. /// /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. #[pallet::storage] #[pallet::getter(fn nominators)] pub type Nominators = CountedStorageMap<_, Twox64Concat, T::AccountId, Nominations>; /// Stakers whose funds are managed by other pallets. /// /// This pallet does not apply any locks on them, therefore they are only virtually bonded. They /// are expected to be keyless accounts and hence should not be allowed to mutate their ledger /// directly via this pallet. Instead, these accounts are managed by other pallets and accessed /// via low level apis. We keep track of them to do minimal integrity checks. #[pallet::storage] pub type VirtualStakers = CountedStorageMap<_, Twox64Concat, T::AccountId, ()>; /// The maximum nominator count before we stop allowing new validators to join. /// /// When this value is not set, no limits are enforced. #[pallet::storage] pub type MaxNominatorsCount = StorageValue<_, u32, OptionQuery>; /// The current era index. /// /// This is the latest planned era, depending on how the Session pallet queues the validator /// set, it might be active or not. #[pallet::storage] #[pallet::getter(fn current_era)] pub type CurrentEra = StorageValue<_, EraIndex>; /// The active era information, it holds index and start. /// /// The active era is the era being currently rewarded. Validator set of this era must be /// equal to [`SessionInterface::validators`]. #[pallet::storage] #[pallet::getter(fn active_era)] pub type ActiveEra = StorageValue<_, ActiveEraInfo>; /// The session index at which the era start for the last [`Config::HistoryDepth`] eras. /// /// Note: This tracks the starting session (i.e. session index when era start being active) /// for the eras in `[CurrentEra - HISTORY_DEPTH, CurrentEra]`. #[pallet::storage] #[pallet::getter(fn eras_start_session_index)] pub type ErasStartSessionIndex = StorageMap<_, Twox64Concat, EraIndex, SessionIndex>; /// Exposure of validator at era. /// /// This is keyed first by the era index to allow bulk deletion and then the stash account. /// /// Is it removed after [`Config::HistoryDepth`] eras. /// If stakers hasn't been set or has been removed then empty exposure is returned. /// /// Note: Deprecated since v14. Use `EraInfo` instead to work with exposures. #[pallet::storage] #[pallet::unbounded] pub type ErasStakers = StorageDoubleMap< _, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, Exposure>, ValueQuery, >; /// Summary of validator exposure at a given era. /// /// This contains the total stake in support of the validator and their own stake. In addition, /// it can also be used to get the number of nominators backing this validator and the number of /// exposure pages they are divided into. The page count is useful to determine the number of /// pages of rewards that needs to be claimed. /// /// This is keyed first by the era index to allow bulk deletion and then the stash account. /// Should only be accessed through `EraInfo`. /// /// Is it removed after [`Config::HistoryDepth`] eras. /// If stakers hasn't been set or has been removed then empty overview is returned. #[pallet::storage] pub type ErasStakersOverview = StorageDoubleMap< _, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, PagedExposureMetadata>, OptionQuery, >; /// Clipped Exposure of validator at era. /// /// Note: This is deprecated, should be used as read-only and will be removed in the future. /// New `Exposure`s are stored in a paged manner in `ErasStakersPaged` instead. /// /// This is similar to [`ErasStakers`] but number of nominators exposed is reduced to the /// `T::MaxExposurePageSize` biggest stakers. /// (Note: the field `total` and `own` of the exposure remains unchanged). /// This is used to limit the i/o cost for the nominator payout. /// /// This is keyed fist by the era index to allow bulk deletion and then the stash account. /// /// It is removed after [`Config::HistoryDepth`] eras. /// If stakers hasn't been set or has been removed then empty exposure is returned. /// /// Note: Deprecated since v14. Use `EraInfo` instead to work with exposures. #[pallet::storage] #[pallet::unbounded] #[pallet::getter(fn eras_stakers_clipped)] pub type ErasStakersClipped = StorageDoubleMap< _, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, Exposure>, ValueQuery, >; /// Paginated exposure of a validator at given era. /// /// This is keyed first by the era index to allow bulk deletion, then stash account and finally /// the page. Should only be accessed through `EraInfo`. /// /// This is cleared after [`Config::HistoryDepth`] eras. #[pallet::storage] #[pallet::unbounded] pub type ErasStakersPaged = StorageNMap< _, ( NMapKey, NMapKey, NMapKey, ), ExposurePage>, OptionQuery, >; /// History of claimed paged rewards by era and validator. /// /// This is keyed by era and validator stash which maps to the set of page indexes which have /// been claimed. /// /// It is removed after [`Config::HistoryDepth`] eras. #[pallet::storage] #[pallet::getter(fn claimed_rewards)] #[pallet::unbounded] pub type ClaimedRewards = StorageDoubleMap< _, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, Vec, ValueQuery, >; /// Similar to `ErasStakers`, this holds the preferences of validators. /// /// This is keyed first by the era index to allow bulk deletion and then the stash account. /// /// Is it removed after [`Config::HistoryDepth`] eras. // If prefs hasn't been set or has been removed then 0 commission is returned. #[pallet::storage] #[pallet::getter(fn eras_validator_prefs)] pub type ErasValidatorPrefs = StorageDoubleMap< _, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, ValidatorPrefs, ValueQuery, >; /// The total validator era payout for the last [`Config::HistoryDepth`] eras. /// /// Eras that haven't finished yet or has been removed doesn't have reward. #[pallet::storage] #[pallet::getter(fn eras_validator_reward)] pub type ErasValidatorReward = StorageMap<_, Twox64Concat, EraIndex, BalanceOf>; /// Rewards for the last [`Config::HistoryDepth`] eras. /// If reward hasn't been set or has been removed then 0 reward is returned. #[pallet::storage] #[pallet::unbounded] #[pallet::getter(fn eras_reward_points)] pub type ErasRewardPoints = StorageMap<_, Twox64Concat, EraIndex, EraRewardPoints, ValueQuery>; /// The total amount staked for the last [`Config::HistoryDepth`] eras. /// If total hasn't been set or has been removed then 0 stake is returned. #[pallet::storage] #[pallet::getter(fn eras_total_stake)] pub type ErasTotalStake = StorageMap<_, Twox64Concat, EraIndex, BalanceOf, ValueQuery>; /// Mode of era forcing. #[pallet::storage] #[pallet::getter(fn force_era)] pub type ForceEra = StorageValue<_, Forcing, ValueQuery>; /// Maximum staked rewards, i.e. the percentage of the era inflation that /// is used for stake rewards. /// See [Era payout](./index.html#era-payout). #[pallet::storage] pub type MaxStakedRewards = StorageValue<_, Percent, OptionQuery>; /// The percentage of the slash that is distributed to reporters. /// /// The rest of the slashed value is handled by the `Slash`. #[pallet::storage] #[pallet::getter(fn slash_reward_fraction)] pub type SlashRewardFraction = StorageValue<_, Perbill, ValueQuery>; /// The amount of currency given to reporters of a slash event which was /// canceled by extraordinary circumstances (e.g. governance). #[pallet::storage] #[pallet::getter(fn canceled_payout)] pub type CanceledSlashPayout = StorageValue<_, BalanceOf, ValueQuery>; /// All unapplied slashes that are queued for later. #[pallet::storage] #[pallet::unbounded] pub type UnappliedSlashes = StorageMap< _, Twox64Concat, EraIndex, Vec>>, ValueQuery, >; /// A mapping from still-bonded eras to the first session index of that era. /// /// Must contains information for eras for the range: /// `[active_era - bounding_duration; active_era]` #[pallet::storage] #[pallet::unbounded] pub(crate) type BondedEras = StorageValue<_, Vec<(EraIndex, SessionIndex)>, ValueQuery>; /// All slashing events on validators, mapped by era to the highest slash proportion /// and slash value of the era. #[pallet::storage] pub(crate) type ValidatorSlashInEra = StorageDoubleMap< _, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, (Perbill, BalanceOf), >; /// All slashing events on nominators, mapped by era to the highest slash value of the era. #[pallet::storage] pub(crate) type NominatorSlashInEra = StorageDoubleMap<_, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, BalanceOf>; /// Slashing spans for stash accounts. #[pallet::storage] #[pallet::getter(fn slashing_spans)] #[pallet::unbounded] pub type SlashingSpans = StorageMap<_, Twox64Concat, T::AccountId, slashing::SlashingSpans>; /// Records information about the maximum slash of a stash within a slashing span, /// as well as how much reward has been paid out. #[pallet::storage] pub(crate) type SpanSlash = StorageMap< _, Twox64Concat, (T::AccountId, slashing::SpanIndex), slashing::SpanRecord>, ValueQuery, >; /// The last planned session scheduled by the session pallet. /// /// This is basically in sync with the call to [`pallet_session::SessionManager::new_session`]. #[pallet::storage] #[pallet::getter(fn current_planned_session)] pub type CurrentPlannedSession = StorageValue<_, SessionIndex, ValueQuery>; /// Indices of validators that have offended in the active era. The offenders are disabled for a /// whole era. For this reason they are kept here - only staking pallet knows about eras. The /// implementor of [`DisablingStrategy`] defines if a validator should be disabled which /// implicitly means that the implementor also controls the max number of disabled validators. /// /// The vec is always kept sorted so that we can find whether a given validator has previously /// offended using binary search. #[pallet::storage] #[pallet::unbounded] pub type DisabledValidators = StorageValue<_, Vec, ValueQuery>; /// The threshold for when users can start calling `chill_other` for other validators / /// nominators. The threshold is compared to the actual number of validators / nominators /// (`CountFor*`) in the system compared to the configured max (`Max*Count`). #[pallet::storage] pub(crate) type ChillThreshold = StorageValue<_, Percent, OptionQuery>; #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub validator_count: u32, pub minimum_validator_count: u32, pub invulnerables: Vec, pub force_era: Forcing, pub slash_reward_fraction: Perbill, pub canceled_payout: BalanceOf, pub stakers: Vec<(T::AccountId, T::AccountId, BalanceOf, crate::StakerStatus)>, pub min_nominator_bond: BalanceOf, pub min_validator_bond: BalanceOf, pub max_validator_count: Option, pub max_nominator_count: Option, } #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { ValidatorCount::::put(self.validator_count); MinimumValidatorCount::::put(self.minimum_validator_count); Invulnerables::::put(&self.invulnerables); ForceEra::::put(self.force_era); CanceledSlashPayout::::put(self.canceled_payout); SlashRewardFraction::::put(self.slash_reward_fraction); MinNominatorBond::::put(self.min_nominator_bond); MinValidatorBond::::put(self.min_validator_bond); if let Some(x) = self.max_validator_count { MaxValidatorsCount::::put(x); } if let Some(x) = self.max_nominator_count { MaxNominatorsCount::::put(x); } for &(ref stash, _, balance, ref status) in &self.stakers { crate::log!( trace, "inserting genesis staker: {:?} => {:?} => {:?}", stash, balance, status ); assert!( T::Currency::free_balance(stash) >= balance, "Stash does not have enough balance to bond." ); frame_support::assert_ok!(>::bond( T::RuntimeOrigin::from(Some(stash.clone()).into()), balance, RewardDestination::Staked, )); frame_support::assert_ok!(match status { crate::StakerStatus::Validator => >::validate( T::RuntimeOrigin::from(Some(stash.clone()).into()), Default::default(), ), crate::StakerStatus::Nominator(votes) => >::nominate( T::RuntimeOrigin::from(Some(stash.clone()).into()), votes.iter().map(|l| T::Lookup::unlookup(l.clone())).collect(), ), _ => Ok(()), }); assert!( ValidatorCount::::get() <= ::MaxWinners::get() ); } // all voters are reported to the `VoterList`. assert_eq!( T::VoterList::count(), Nominators::::count() + Validators::::count(), "not all genesis stakers were inserted into sorted list provider, something is wrong." ); } } #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { /// The era payout has been set; the first balance is the validator-payout; the second is /// the remainder from the maximum amount of reward. EraPaid { era_index: EraIndex, validator_payout: BalanceOf, remainder: BalanceOf }, /// The nominator has been rewarded by this amount to this destination. Rewarded { stash: T::AccountId, dest: RewardDestination, amount: BalanceOf, }, /// A staker (validator or nominator) has been slashed by the given amount. Slashed { staker: T::AccountId, amount: BalanceOf }, /// A slash for the given validator, for the given percentage of their stake, at the given /// era as been reported. SlashReported { validator: T::AccountId, fraction: Perbill, slash_era: EraIndex }, /// An old slashing report from a prior era was discarded because it could /// not be processed. OldSlashingReportDiscarded { session_index: SessionIndex }, /// A new set of stakers was elected. StakersElected, /// An account has bonded this amount. \[stash, amount\] /// /// NOTE: This event is only emitted when funds are bonded via a dispatchable. Notably, /// it will not be emitted for staking rewards when they are added to stake. Bonded { stash: T::AccountId, amount: BalanceOf }, /// An account has unbonded this amount. Unbonded { stash: T::AccountId, amount: BalanceOf }, /// An account has called `withdraw_unbonded` and removed unbonding chunks worth `Balance` /// from the unlocking queue. Withdrawn { stash: T::AccountId, amount: BalanceOf }, /// A nominator has been kicked from a validator. Kicked { nominator: T::AccountId, stash: T::AccountId }, /// The election failed. No new era is planned. StakingElectionFailed, /// An account has stopped participating as either a validator or nominator. Chilled { stash: T::AccountId }, /// The stakers' rewards are getting paid. PayoutStarted { era_index: EraIndex, validator_stash: T::AccountId }, /// A validator has set their preferences. ValidatorPrefsSet { stash: T::AccountId, prefs: ValidatorPrefs }, /// Voters size limit reached. SnapshotVotersSizeExceeded { size: u32 }, /// Targets size limit reached. SnapshotTargetsSizeExceeded { size: u32 }, /// A new force era mode was set. ForceEra { mode: Forcing }, /// Report of a controller batch deprecation. ControllerBatchDeprecated { failures: u32 }, } #[pallet::error] #[derive(PartialEq)] pub enum Error { /// Not a controller account. NotController, /// Not a stash account. NotStash, /// Stash is already bonded. AlreadyBonded, /// Controller is already paired. AlreadyPaired, /// Targets cannot be empty. EmptyTargets, /// Duplicate index. DuplicateIndex, /// Slash record index out of bounds. InvalidSlashIndex, /// Cannot have a validator or nominator role, with value less than the minimum defined by /// governance (see `MinValidatorBond` and `MinNominatorBond`). If unbonding is the /// intention, `chill` first to remove one's role as validator/nominator. InsufficientBond, /// Can not schedule more unlock chunks. NoMoreChunks, /// Can not rebond without unlocking chunks. NoUnlockChunk, /// Attempting to target a stash that still has funds. FundedTarget, /// Invalid era to reward. InvalidEraToReward, /// Invalid number of nominations. InvalidNumberOfNominations, /// Items are not sorted and unique. NotSortedAndUnique, /// Rewards for this era have already been claimed for this validator. AlreadyClaimed, /// No nominators exist on this page. InvalidPage, /// Incorrect previous history depth input provided. IncorrectHistoryDepth, /// Incorrect number of slashing spans provided. IncorrectSlashingSpans, /// Internal state has become somehow corrupted and the operation cannot continue. BadState, /// Too many nomination targets supplied. TooManyTargets, /// A nomination target was supplied that was blocked or otherwise not a validator. BadTarget, /// The user has enough bond and thus cannot be chilled forcefully by an external person. CannotChillOther, /// There are too many nominators in the system. Governance needs to adjust the staking /// settings to keep things safe for the runtime. TooManyNominators, /// There are too many validator candidates in the system. Governance needs to adjust the /// staking settings to keep things safe for the runtime. TooManyValidators, /// Commission is too low. Must be at least `MinCommission`. CommissionTooLow, /// Some bound is not met. BoundNotMet, /// Used when attempting to use deprecated controller account logic. ControllerDeprecated, /// Cannot reset a ledger. CannotRestoreLedger, /// Provided reward destination is not allowed. RewardDestinationRestricted, /// Not enough funds available to withdraw. NotEnoughFunds, } #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(_now: BlockNumberFor) -> Weight { // just return the weight of the on_finalize. T::DbWeight::get().reads(1) } fn on_finalize(_n: BlockNumberFor) { // Set the start of the first era. if let Some(mut active_era) = Self::active_era() { if active_era.start.is_none() { let now_as_millis_u64 = T::UnixTime::now().as_millis().saturated_into::(); active_era.start = Some(now_as_millis_u64); // This write only ever happens once, we don't include it in the weight in // general ActiveEra::::put(active_era); } } // `on_finalize` weight is tracked in `on_initialize` } fn integrity_test() { // ensure that we funnel the correct value to the `DataProvider::MaxVotesPerVoter`; assert_eq!( MaxNominationsOf::::get(), ::MaxVotesPerVoter::get() ); // and that MaxNominations is always greater than 1, since we count on this. assert!(!MaxNominationsOf::::get().is_zero()); // ensure election results are always bounded with the same value assert!( ::MaxWinners::get() == ::MaxWinners::get() ); assert!( T::SlashDeferDuration::get() < T::BondingDuration::get() || T::BondingDuration::get() == 0, "As per documentation, slash defer duration ({}) should be less than bonding duration ({}).", T::SlashDeferDuration::get(), T::BondingDuration::get(), ) } #[cfg(feature = "try-runtime")] fn try_state(n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { Self::do_try_state(n) } } #[pallet::call] impl Pallet { /// Take the origin account as a stash and lock up `value` of its balance. `controller` will /// be the account that controls it. /// /// `value` must be more than the `minimum_balance` specified by `T::Currency`. /// /// The dispatch origin for this call must be _Signed_ by the stash account. /// /// Emits `Bonded`. /// ## Complexity /// - Independent of the arguments. Moderate complexity. /// - O(1). /// - Three extra DB entries. /// /// NOTE: Two of the storage writes (`Self::bonded`, `Self::payee`) are _never_ cleaned /// unless the `origin` falls below _existential deposit_ and gets removed as dust. #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::bond())] pub fn bond( origin: OriginFor, #[pallet::compact] value: BalanceOf, payee: RewardDestination, ) -> DispatchResult { let stash = ensure_signed(origin)?; if StakingLedger::::is_bonded(StakingAccount::Stash(stash.clone())) { return Err(Error::::AlreadyBonded.into()) } // An existing controller cannot become a stash. if StakingLedger::::is_bonded(StakingAccount::Controller(stash.clone())) { return Err(Error::::AlreadyPaired.into()) } // Reject a bond which is considered to be _dust_. if value < T::Currency::minimum_balance() { return Err(Error::::InsufficientBond.into()) } frame_system::Pallet::::inc_consumers(&stash).map_err(|_| Error::::BadState)?; let stash_balance = T::Currency::free_balance(&stash); let value = value.min(stash_balance); Self::deposit_event(Event::::Bonded { stash: stash.clone(), amount: value }); let ledger = StakingLedger::::new(stash.clone(), value); // You're auto-bonded forever, here. We might improve this by only bonding when // you actually validate/nominate and remove once you unbond __everything__. ledger.bond(payee)?; Ok(()) } /// Add some extra amount that have appeared in the stash `free_balance` into the balance up /// for staking. /// /// The dispatch origin for this call must be _Signed_ by the stash, not the controller. /// /// Use this if there are additional funds in your stash account that you wish to bond. /// Unlike [`bond`](Self::bond) or [`unbond`](Self::unbond) this function does not impose /// any limitation on the amount that can be added. /// /// Emits `Bonded`. /// /// ## Complexity /// - Independent of the arguments. Insignificant complexity. /// - O(1). #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::bond_extra())] pub fn bond_extra( origin: OriginFor, #[pallet::compact] max_additional: BalanceOf, ) -> DispatchResult { let stash = ensure_signed(origin)?; Self::do_bond_extra(&stash, max_additional) } /// Schedule a portion of the stash to be unlocked ready for transfer out after the bond /// period ends. If this leaves an amount actively bonded less than /// T::Currency::minimum_balance(), then it is increased to the full amount. /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. /// /// Once the unlock period is done, you can call `withdraw_unbonded` to actually move /// the funds out of management ready for transfer. /// /// No more than a limited number of unlocking chunks (see `MaxUnlockingChunks`) /// can co-exists at the same time. If there are no unlocking chunks slots available /// [`Call::withdraw_unbonded`] is called to remove some of the chunks (if possible). /// /// If a user encounters the `InsufficientBond` error when calling this extrinsic, /// they should call `chill` first in order to free up their bonded funds. /// /// Emits `Unbonded`. /// /// See also [`Call::withdraw_unbonded`]. #[pallet::call_index(2)] #[pallet::weight( T::WeightInfo::withdraw_unbonded_kill(SPECULATIVE_NUM_SPANS).saturating_add(T::WeightInfo::unbond())) ] pub fn unbond( origin: OriginFor, #[pallet::compact] value: BalanceOf, ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; let unlocking = Self::ledger(Controller(controller.clone())).map(|l| l.unlocking.len())?; // if there are no unlocking chunks available, try to withdraw chunks older than // `BondingDuration` to proceed with the unbonding. let maybe_withdraw_weight = { if unlocking == T::MaxUnlockingChunks::get() as usize { let real_num_slashing_spans = Self::slashing_spans(&controller).map_or(0, |s| s.iter().count()); Some(Self::do_withdraw_unbonded(&controller, real_num_slashing_spans as u32)?) } else { None } }; // we need to fetch the ledger again because it may have been mutated in the call // to `Self::do_withdraw_unbonded` above. let mut ledger = Self::ledger(Controller(controller))?; let mut value = value.min(ledger.active); let stash = ledger.stash.clone(); ensure!( ledger.unlocking.len() < T::MaxUnlockingChunks::get() as usize, Error::::NoMoreChunks, ); if !value.is_zero() { ledger.active -= value; // Avoid there being a dust balance left in the staking system. if ledger.active < T::Currency::minimum_balance() { value += ledger.active; ledger.active = Zero::zero(); } let min_active_bond = if Nominators::::contains_key(&stash) { MinNominatorBond::::get() } else if Validators::::contains_key(&stash) { MinValidatorBond::::get() } else { Zero::zero() }; // Make sure that the user maintains enough active bond for their role. // If a user runs into this error, they should chill first. ensure!(ledger.active >= min_active_bond, Error::::InsufficientBond); // Note: in case there is no current era it is fine to bond one era more. let era = Self::current_era() .unwrap_or(0) .defensive_saturating_add(T::BondingDuration::get()); if let Some(chunk) = ledger.unlocking.last_mut().filter(|chunk| chunk.era == era) { // To keep the chunk count down, we only keep one chunk per era. Since // `unlocking` is a FiFo queue, if a chunk exists for `era` we know that it will // be the last one. chunk.value = chunk.value.defensive_saturating_add(value) } else { ledger .unlocking .try_push(UnlockChunk { value, era }) .map_err(|_| Error::::NoMoreChunks)?; }; // NOTE: ledger must be updated prior to calling `Self::weight_of`. ledger.update()?; // update this staker in the sorted list, if they exist in it. if T::VoterList::contains(&stash) { let _ = T::VoterList::on_update(&stash, Self::weight_of(&stash)).defensive(); } Self::deposit_event(Event::::Unbonded { stash, amount: value }); } let actual_weight = if let Some(withdraw_weight) = maybe_withdraw_weight { Some(T::WeightInfo::unbond().saturating_add(withdraw_weight)) } else { Some(T::WeightInfo::unbond()) }; Ok(actual_weight.into()) } /// Remove any unlocked chunks from the `unlocking` queue from our management. /// /// This essentially frees up that balance to be used by the stash account to do whatever /// it wants. /// /// The dispatch origin for this call must be _Signed_ by the controller. /// /// Emits `Withdrawn`. /// /// See also [`Call::unbond`]. /// /// ## Parameters /// /// - `num_slashing_spans` indicates the number of metadata slashing spans to clear when /// this call results in a complete removal of all the data related to the stash account. /// In this case, the `num_slashing_spans` must be larger or equal to the number of /// slashing spans associated with the stash account in the [`SlashingSpans`] storage type, /// otherwise the call will fail. The call weight is directly proportional to /// `num_slashing_spans`. /// /// ## Complexity /// O(S) where S is the number of slashing spans to remove /// NOTE: Weight annotation is the kill scenario, we refund otherwise. #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans))] pub fn withdraw_unbonded( origin: OriginFor, num_slashing_spans: u32, ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; let actual_weight = Self::do_withdraw_unbonded(&controller, num_slashing_spans)?; Ok(Some(actual_weight).into()) } /// Declare the desire to validate for the origin controller. /// /// Effects will be felt at the beginning of the next era. /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::validate())] pub fn validate(origin: OriginFor, prefs: ValidatorPrefs) -> DispatchResult { let controller = ensure_signed(origin)?; let ledger = Self::ledger(Controller(controller))?; ensure!(ledger.active >= MinValidatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; // ensure their commission is correct. ensure!(prefs.commission >= MinCommission::::get(), Error::::CommissionTooLow); // Only check limits if they are not already a validator. if !Validators::::contains_key(stash) { // If this error is reached, we need to adjust the `MinValidatorBond` and start // calling `chill_other`. Until then, we explicitly block new validators to protect // the runtime. if let Some(max_validators) = MaxValidatorsCount::::get() { ensure!( Validators::::count() < max_validators, Error::::TooManyValidators ); } } Self::do_remove_nominator(stash); Self::do_add_validator(stash, prefs.clone()); Self::deposit_event(Event::::ValidatorPrefsSet { stash: ledger.stash, prefs }); Ok(()) } /// Declare the desire to nominate `targets` for the origin controller. /// /// Effects will be felt at the beginning of the next era. /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. /// /// ## Complexity /// - The transaction's complexity is proportional to the size of `targets` (N) /// which is capped at CompactAssignments::LIMIT (T::MaxNominations). /// - Both the reads and writes follow a similar pattern. #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::nominate(targets.len() as u32))] pub fn nominate( origin: OriginFor, targets: Vec>, ) -> DispatchResult { let controller = ensure_signed(origin)?; let ledger = Self::ledger(StakingAccount::Controller(controller.clone()))?; ensure!(ledger.active >= MinNominatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; // Only check limits if they are not already a nominator. if !Nominators::::contains_key(stash) { // If this error is reached, we need to adjust the `MinNominatorBond` and start // calling `chill_other`. Until then, we explicitly block new nominators to protect // the runtime. if let Some(max_nominators) = MaxNominatorsCount::::get() { ensure!( Nominators::::count() < max_nominators, Error::::TooManyNominators ); } } ensure!(!targets.is_empty(), Error::::EmptyTargets); ensure!( targets.len() <= T::NominationsQuota::get_quota(ledger.active) as usize, Error::::TooManyTargets ); let old = Nominators::::get(stash).map_or_else(Vec::new, |x| x.targets.into_inner()); let targets: BoundedVec<_, _> = targets .into_iter() .map(|t| T::Lookup::lookup(t).map_err(DispatchError::from)) .map(|n| { n.and_then(|n| { if old.contains(&n) || !Validators::::get(&n).blocked { Ok(n) } else { Err(Error::::BadTarget.into()) } }) }) .collect::, _>>()? .try_into() .map_err(|_| Error::::TooManyNominators)?; let nominations = Nominations { targets, // Initial nominations are considered submitted at era 0. See `Nominations` doc. submitted_in: Self::current_era().unwrap_or(0), suppressed: false, }; Self::do_remove_validator(stash); Self::do_add_nominator(stash, nominations); Ok(()) } /// Declare no desire to either validate or nominate. /// /// Effects will be felt at the beginning of the next era. /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. /// /// ## Complexity /// - Independent of the arguments. Insignificant complexity. /// - Contains one read. /// - Writes are limited to the `origin` account key. #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::chill())] pub fn chill(origin: OriginFor) -> DispatchResult { let controller = ensure_signed(origin)?; let ledger = Self::ledger(StakingAccount::Controller(controller))?; Self::chill_stash(&ledger.stash); Ok(()) } /// (Re-)set the payment target for a controller. /// /// Effects will be felt instantly (as soon as this function is completed successfully). /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. /// /// ## Complexity /// - O(1) /// - Independent of the arguments. Insignificant complexity. /// - Contains a limited number of reads. /// - Writes are limited to the `origin` account key. /// --------- #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::set_payee())] pub fn set_payee( origin: OriginFor, payee: RewardDestination, ) -> DispatchResult { let controller = ensure_signed(origin)?; let ledger = Self::ledger(Controller(controller.clone()))?; ensure!( (payee != { #[allow(deprecated)] RewardDestination::Controller }), Error::::ControllerDeprecated ); let _ = ledger .set_payee(payee) .defensive_proof("ledger was retrieved from storage, thus its bonded; qed.")?; Ok(()) } /// (Re-)sets the controller of a stash to the stash itself. This function previously /// accepted a `controller` argument to set the controller to an account other than the /// stash itself. This functionality has now been removed, now only setting the controller /// to the stash, if it is not already. /// /// Effects will be felt instantly (as soon as this function is completed successfully). /// /// The dispatch origin for this call must be _Signed_ by the stash, not the controller. /// /// ## Complexity /// O(1) /// - Independent of the arguments. Insignificant complexity. /// - Contains a limited number of reads. /// - Writes are limited to the `origin` account key. #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::set_controller())] pub fn set_controller(origin: OriginFor) -> DispatchResult { let stash = ensure_signed(origin)?; Self::ledger(StakingAccount::Stash(stash.clone())).map(|ledger| { let controller = ledger.controller() .defensive_proof("Ledger's controller field didn't exist. The controller should have been fetched using StakingLedger.") .ok_or(Error::::NotController)?; if controller == stash { // Stash is already its own controller. return Err(Error::::AlreadyPaired.into()) } let _ = ledger.set_controller_to_stash()?; Ok(()) })? } /// Sets the ideal number of validators. /// /// The dispatch origin must be Root. /// /// ## Complexity /// O(1) #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn set_validator_count( origin: OriginFor, #[pallet::compact] new: u32, ) -> DispatchResult { ensure_root(origin)?; // ensure new validator count does not exceed maximum winners // support by election provider. ensure!( new <= ::MaxWinners::get(), Error::::TooManyValidators ); ValidatorCount::::put(new); Ok(()) } /// Increments the ideal number of validators up to maximum of /// `ElectionProviderBase::MaxWinners`. /// /// The dispatch origin must be Root. /// /// ## Complexity /// Same as [`Self::set_validator_count`]. #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn increase_validator_count( origin: OriginFor, #[pallet::compact] additional: u32, ) -> DispatchResult { ensure_root(origin)?; let old = ValidatorCount::::get(); let new = old.checked_add(additional).ok_or(ArithmeticError::Overflow)?; ensure!( new <= ::MaxWinners::get(), Error::::TooManyValidators ); ValidatorCount::::put(new); Ok(()) } /// Scale up the ideal number of validators by a factor up to maximum of /// `ElectionProviderBase::MaxWinners`. /// /// The dispatch origin must be Root. /// /// ## Complexity /// Same as [`Self::set_validator_count`]. #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn scale_validator_count(origin: OriginFor, factor: Percent) -> DispatchResult { ensure_root(origin)?; let old = ValidatorCount::::get(); let new = old.checked_add(factor.mul_floor(old)).ok_or(ArithmeticError::Overflow)?; ensure!( new <= ::MaxWinners::get(), Error::::TooManyValidators ); ValidatorCount::::put(new); Ok(()) } /// Force there to be no new eras indefinitely. /// /// The dispatch origin must be Root. /// /// # Warning /// /// The election process starts multiple blocks before the end of the era. /// Thus the election process may be ongoing when this is called. In this case the /// election will continue until the next era is triggered. /// /// ## Complexity /// - No arguments. /// - Weight: O(1) #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::force_no_eras())] pub fn force_no_eras(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; Self::set_force_era(Forcing::ForceNone); Ok(()) } /// Force there to be a new era at the end of the next session. After this, it will be /// reset to normal (non-forced) behaviour. /// /// The dispatch origin must be Root. /// /// # Warning /// /// The election process starts multiple blocks before the end of the era. /// If this is called just before a new era is triggered, the election process may not /// have enough blocks to get a result. /// /// ## Complexity /// - No arguments. /// - Weight: O(1) #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::force_new_era())] pub fn force_new_era(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; Self::set_force_era(Forcing::ForceNew); Ok(()) } /// Set the validators who cannot be slashed (if any). /// /// The dispatch origin must be Root. #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::set_invulnerables(invulnerables.len() as u32))] pub fn set_invulnerables( origin: OriginFor, invulnerables: Vec, ) -> DispatchResult { ensure_root(origin)?; >::put(invulnerables); Ok(()) } /// Force a current staker to become completely unstaked, immediately. /// /// The dispatch origin must be Root. /// /// ## Parameters /// /// - `num_slashing_spans`: Refer to comments on [`Call::withdraw_unbonded`] for more /// details. #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::force_unstake(*num_slashing_spans))] pub fn force_unstake( origin: OriginFor, stash: T::AccountId, num_slashing_spans: u32, ) -> DispatchResult { ensure_root(origin)?; // Remove all staking-related information and lock. Self::kill_stash(&stash, num_slashing_spans)?; Ok(()) } /// Force there to be a new era at the end of sessions indefinitely. /// /// The dispatch origin must be Root. /// /// # Warning /// /// The election process starts multiple blocks before the end of the era. /// If this is called just before a new era is triggered, the election process may not /// have enough blocks to get a result. #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::force_new_era_always())] pub fn force_new_era_always(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; Self::set_force_era(Forcing::ForceAlways); Ok(()) } /// Cancel enactment of a deferred slash. /// /// Can be called by the `T::AdminOrigin`. /// /// Parameters: era and indices of the slashes for that era to kill. #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::cancel_deferred_slash(slash_indices.len() as u32))] pub fn cancel_deferred_slash( origin: OriginFor, era: EraIndex, slash_indices: Vec, ) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; ensure!(!slash_indices.is_empty(), Error::::EmptyTargets); ensure!(is_sorted_and_unique(&slash_indices), Error::::NotSortedAndUnique); let mut unapplied = UnappliedSlashes::::get(&era); let last_item = slash_indices[slash_indices.len() - 1]; ensure!((last_item as usize) < unapplied.len(), Error::::InvalidSlashIndex); for (removed, index) in slash_indices.into_iter().enumerate() { let index = (index as usize) - removed; unapplied.remove(index); } UnappliedSlashes::::insert(&era, &unapplied); Ok(()) } /// Pay out next page of the stakers behind a validator for the given era. /// /// - `validator_stash` is the stash account of the validator. /// - `era` may be any era between `[current_era - history_depth; current_era]`. /// /// The origin of this call must be _Signed_. Any account can call this function, even if /// it is not one of the stakers. /// /// The reward payout could be paged in case there are too many nominators backing the /// `validator_stash`. This call will payout unpaid pages in an ascending order. To claim a /// specific page, use `payout_stakers_by_page`.` /// /// If all pages are claimed, it returns an error `InvalidPage`. #[pallet::call_index(18)] #[pallet::weight(T::WeightInfo::payout_stakers_alive_staked(T::MaxExposurePageSize::get()))] pub fn payout_stakers( origin: OriginFor, validator_stash: T::AccountId, era: EraIndex, ) -> DispatchResultWithPostInfo { ensure_signed(origin)?; Self::do_payout_stakers(validator_stash, era) } /// Rebond a portion of the stash scheduled to be unlocked. /// /// The dispatch origin must be signed by the controller. /// /// ## Complexity /// - Time complexity: O(L), where L is unlocking chunks /// - Bounded by `MaxUnlockingChunks`. #[pallet::call_index(19)] #[pallet::weight(T::WeightInfo::rebond(T::MaxUnlockingChunks::get() as u32))] pub fn rebond( origin: OriginFor, #[pallet::compact] value: BalanceOf, ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; let ledger = Self::ledger(Controller(controller))?; ensure!(!ledger.unlocking.is_empty(), Error::::NoUnlockChunk); let initial_unlocking = ledger.unlocking.len() as u32; let (ledger, rebonded_value) = ledger.rebond(value); // Last check: the new active amount of ledger must be more than ED. ensure!(ledger.active >= T::Currency::minimum_balance(), Error::::InsufficientBond); Self::deposit_event(Event::::Bonded { stash: ledger.stash.clone(), amount: rebonded_value, }); let stash = ledger.stash.clone(); let final_unlocking = ledger.unlocking.len(); // NOTE: ledger must be updated prior to calling `Self::weight_of`. ledger.update()?; if T::VoterList::contains(&stash) { let _ = T::VoterList::on_update(&stash, Self::weight_of(&stash)).defensive(); } let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed .saturating_add(initial_unlocking) .saturating_sub(final_unlocking as u32); Ok(Some(T::WeightInfo::rebond(removed_chunks)).into()) } /// Remove all data structures concerning a staker/stash once it is at a state where it can /// be considered `dust` in the staking system. The requirements are: /// /// 1. the `total_balance` of the stash is below existential deposit. /// 2. or, the `ledger.total` of the stash is below existential deposit. /// /// The former can happen in cases like a slash; the latter when a fully unbonded account /// is still receiving staking rewards in `RewardDestination::Staked`. /// /// It can be called by anyone, as long as `stash` meets the above requirements. /// /// Refunds the transaction fees upon successful execution. /// /// ## Parameters /// /// - `num_slashing_spans`: Refer to comments on [`Call::withdraw_unbonded`] for more /// details. #[pallet::call_index(20)] #[pallet::weight(T::WeightInfo::reap_stash(*num_slashing_spans))] pub fn reap_stash( origin: OriginFor, stash: T::AccountId, num_slashing_spans: u32, ) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; let ed = T::Currency::minimum_balance(); let reapable = T::Currency::total_balance(&stash) < ed || Self::ledger(Stash(stash.clone())).map(|l| l.total).unwrap_or_default() < ed; ensure!(reapable, Error::::FundedTarget); // Remove all staking-related information and lock. Self::kill_stash(&stash, num_slashing_spans)?; Ok(Pays::No.into()) } /// Remove the given nominations from the calling validator. /// /// Effects will be felt at the beginning of the next era. /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. /// /// - `who`: A list of nominator stash accounts who are nominating this validator which /// should no longer be nominating this validator. /// /// Note: Making this call only makes sense if you first set the validator preferences to /// block any further nominations. #[pallet::call_index(21)] #[pallet::weight(T::WeightInfo::kick(who.len() as u32))] pub fn kick(origin: OriginFor, who: Vec>) -> DispatchResult { let controller = ensure_signed(origin)?; let ledger = Self::ledger(Controller(controller))?; let stash = &ledger.stash; for nom_stash in who .into_iter() .map(T::Lookup::lookup) .collect::, _>>()? .into_iter() { Nominators::::mutate(&nom_stash, |maybe_nom| { if let Some(ref mut nom) = maybe_nom { if let Some(pos) = nom.targets.iter().position(|v| v == stash) { nom.targets.swap_remove(pos); Self::deposit_event(Event::::Kicked { nominator: nom_stash.clone(), stash: stash.clone(), }); } } }); } Ok(()) } /// Update the various staking configurations . /// /// * `min_nominator_bond`: The minimum active bond needed to be a nominator. /// * `min_validator_bond`: The minimum active bond needed to be a validator. /// * `max_nominator_count`: The max number of users who can be a nominator at once. When /// set to `None`, no limit is enforced. /// * `max_validator_count`: The max number of users who can be a validator at once. When /// set to `None`, no limit is enforced. /// * `chill_threshold`: The ratio of `max_nominator_count` or `max_validator_count` which /// should be filled in order for the `chill_other` transaction to work. /// * `min_commission`: The minimum amount of commission that each validators must maintain. /// This is checked only upon calling `validate`. Existing validators are not affected. /// /// RuntimeOrigin must be Root to call this function. /// /// NOTE: Existing nominators and validators will not be affected by this update. /// to kick people under the new limits, `chill_other` should be called. // We assume the worst case for this call is either: all items are set or all items are // removed. #[pallet::call_index(22)] #[pallet::weight( T::WeightInfo::set_staking_configs_all_set() .max(T::WeightInfo::set_staking_configs_all_remove()) )] pub fn set_staking_configs( origin: OriginFor, min_nominator_bond: ConfigOp>, min_validator_bond: ConfigOp>, max_nominator_count: ConfigOp, max_validator_count: ConfigOp, chill_threshold: ConfigOp, min_commission: ConfigOp, max_staked_rewards: ConfigOp, ) -> DispatchResult { ensure_root(origin)?; macro_rules! config_op_exp { ($storage:ty, $op:ident) => { match $op { ConfigOp::Noop => (), ConfigOp::Set(v) => <$storage>::put(v), ConfigOp::Remove => <$storage>::kill(), } }; } config_op_exp!(MinNominatorBond, min_nominator_bond); config_op_exp!(MinValidatorBond, min_validator_bond); config_op_exp!(MaxNominatorsCount, max_nominator_count); config_op_exp!(MaxValidatorsCount, max_validator_count); config_op_exp!(ChillThreshold, chill_threshold); config_op_exp!(MinCommission, min_commission); config_op_exp!(MaxStakedRewards, max_staked_rewards); Ok(()) } /// Declare a `controller` to stop participating as either a validator or nominator. /// /// Effects will be felt at the beginning of the next era. /// /// The dispatch origin for this call must be _Signed_, but can be called by anyone. /// /// If the caller is the same as the controller being targeted, then no further checks are /// enforced, and this function behaves just like `chill`. /// /// If the caller is different than the controller being targeted, the following conditions /// must be met: /// /// * `controller` must belong to a nominator who has become non-decodable, /// /// Or: /// /// * A `ChillThreshold` must be set and checked which defines how close to the max /// nominators or validators we must reach before users can start chilling one-another. /// * A `MaxNominatorCount` and `MaxValidatorCount` must be set which is used to determine /// how close we are to the threshold. /// * A `MinNominatorBond` and `MinValidatorBond` must be set and checked, which determines /// if this is a person that should be chilled because they have not met the threshold /// bond required. /// /// This can be helpful if bond requirements are updated, and we need to remove old users /// who do not satisfy these requirements. #[pallet::call_index(23)] #[pallet::weight(T::WeightInfo::chill_other())] pub fn chill_other(origin: OriginFor, stash: T::AccountId) -> DispatchResult { // Anyone can call this function. let caller = ensure_signed(origin)?; let ledger = Self::ledger(Stash(stash.clone()))?; let controller = ledger .controller() .defensive_proof( "Ledger's controller field didn't exist. The controller should have been fetched using StakingLedger.", ) .ok_or(Error::::NotController)?; // In order for one user to chill another user, the following conditions must be met: // // * `controller` belongs to a nominator who has become non-decodable, // // Or // // * A `ChillThreshold` is set which defines how close to the max nominators or // validators we must reach before users can start chilling one-another. // * A `MaxNominatorCount` and `MaxValidatorCount` which is used to determine how close // we are to the threshold. // * A `MinNominatorBond` and `MinValidatorBond` which is the final condition checked to // determine this is a person that should be chilled because they have not met the // threshold bond required. // // Otherwise, if caller is the same as the controller, this is just like `chill`. if Nominators::::contains_key(&stash) && Nominators::::get(&stash).is_none() { Self::chill_stash(&stash); return Ok(()) } if caller != controller { let threshold = ChillThreshold::::get().ok_or(Error::::CannotChillOther)?; let min_active_bond = if Nominators::::contains_key(&stash) { let max_nominator_count = MaxNominatorsCount::::get().ok_or(Error::::CannotChillOther)?; let current_nominator_count = Nominators::::count(); ensure!( threshold * max_nominator_count < current_nominator_count, Error::::CannotChillOther ); MinNominatorBond::::get() } else if Validators::::contains_key(&stash) { let max_validator_count = MaxValidatorsCount::::get().ok_or(Error::::CannotChillOther)?; let current_validator_count = Validators::::count(); ensure!( threshold * max_validator_count < current_validator_count, Error::::CannotChillOther ); MinValidatorBond::::get() } else { Zero::zero() }; ensure!(ledger.active < min_active_bond, Error::::CannotChillOther); } Self::chill_stash(&stash); Ok(()) } /// Force a validator to have at least the minimum commission. This will not affect a /// validator who already has a commission greater than or equal to the minimum. Any account /// can call this. #[pallet::call_index(24)] #[pallet::weight(T::WeightInfo::force_apply_min_commission())] pub fn force_apply_min_commission( origin: OriginFor, validator_stash: T::AccountId, ) -> DispatchResult { ensure_signed(origin)?; let min_commission = MinCommission::::get(); Validators::::try_mutate_exists(validator_stash, |maybe_prefs| { maybe_prefs .as_mut() .map(|prefs| { (prefs.commission < min_commission) .then(|| prefs.commission = min_commission) }) .ok_or(Error::::NotStash) })?; Ok(()) } /// Sets the minimum amount of commission that each validators must maintain. /// /// This call has lower privilege requirements than `set_staking_config` and can be called /// by the `T::AdminOrigin`. Root can always call this. #[pallet::call_index(25)] #[pallet::weight(T::WeightInfo::set_min_commission())] pub fn set_min_commission(origin: OriginFor, new: Perbill) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; MinCommission::::put(new); Ok(()) } /// Pay out a page of the stakers behind a validator for the given era and page. /// /// - `validator_stash` is the stash account of the validator. /// - `era` may be any era between `[current_era - history_depth; current_era]`. /// - `page` is the page index of nominators to pay out with value between 0 and /// `num_nominators / T::MaxExposurePageSize`. /// /// The origin of this call must be _Signed_. Any account can call this function, even if /// it is not one of the stakers. /// /// If a validator has more than [`Config::MaxExposurePageSize`] nominators backing /// them, then the list of nominators is paged, with each page being capped at /// [`Config::MaxExposurePageSize`.] If a validator has more than one page of nominators, /// the call needs to be made for each page separately in order for all the nominators /// backing a validator to receive the reward. The nominators are not sorted across pages /// and so it should not be assumed the highest staker would be on the topmost page and vice /// versa. If rewards are not claimed in [`Config::HistoryDepth`] eras, they are lost. #[pallet::call_index(26)] #[pallet::weight(T::WeightInfo::payout_stakers_alive_staked(T::MaxExposurePageSize::get()))] pub fn payout_stakers_by_page( origin: OriginFor, validator_stash: T::AccountId, era: EraIndex, page: Page, ) -> DispatchResultWithPostInfo { ensure_signed(origin)?; Self::do_payout_stakers_by_page(validator_stash, era, page) } /// Migrates an account's `RewardDestination::Controller` to /// `RewardDestination::Account(controller)`. /// /// Effects will be felt instantly (as soon as this function is completed successfully). /// /// This will waive the transaction fee if the `payee` is successfully migrated. #[pallet::call_index(27)] #[pallet::weight(T::WeightInfo::update_payee())] pub fn update_payee( origin: OriginFor, controller: T::AccountId, ) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; let ledger = Self::ledger(StakingAccount::Controller(controller.clone()))?; ensure!( (Payee::::get(&ledger.stash) == { #[allow(deprecated)] Some(RewardDestination::Controller) }), Error::::NotController ); let _ = ledger .set_payee(RewardDestination::Account(controller)) .defensive_proof("ledger should have been previously retrieved from storage.")?; Ok(Pays::No.into()) } /// Updates a batch of controller accounts to their corresponding stash account if they are /// not the same. Ignores any controller accounts that do not exist, and does not operate if /// the stash and controller are already the same. /// /// Effects will be felt instantly (as soon as this function is completed successfully). /// /// The dispatch origin must be `T::AdminOrigin`. #[pallet::call_index(28)] #[pallet::weight(T::WeightInfo::deprecate_controller_batch(controllers.len() as u32))] pub fn deprecate_controller_batch( origin: OriginFor, controllers: BoundedVec, ) -> DispatchResultWithPostInfo { T::AdminOrigin::ensure_origin(origin)?; // Ignore controllers that do not exist or are already the same as stash. let filtered_batch_with_ledger: Vec<_> = controllers .iter() .filter_map(|controller| { let ledger = Self::ledger(StakingAccount::Controller(controller.clone())); ledger.ok().map_or(None, |ledger| { // If the controller `RewardDestination` is still the deprecated // `Controller` variant, skip deprecating this account. let payee_deprecated = Payee::::get(&ledger.stash) == { #[allow(deprecated)] Some(RewardDestination::Controller) }; if ledger.stash != *controller && !payee_deprecated { Some(ledger) } else { None } }) }) .collect(); // Update unique pairs. let mut failures = 0; for ledger in filtered_batch_with_ledger { let _ = ledger.clone().set_controller_to_stash().map_err(|_| failures += 1); } Self::deposit_event(Event::::ControllerBatchDeprecated { failures }); Ok(Some(T::WeightInfo::deprecate_controller_batch(controllers.len() as u32)).into()) } /// Restores the state of a ledger which is in an inconsistent state. /// /// The requirements to restore a ledger are the following: /// * The stash is bonded; or /// * The stash is not bonded but it has a staking lock left behind; or /// * If the stash has an associated ledger and its state is inconsistent; or /// * If the ledger is not corrupted *but* its staking lock is out of sync. /// /// The `maybe_*` input parameters will overwrite the corresponding data and metadata of the /// ledger associated with the stash. If the input parameters are not set, the ledger will /// be reset values from on-chain state. #[pallet::call_index(29)] #[pallet::weight(T::WeightInfo::restore_ledger())] pub fn restore_ledger( origin: OriginFor, stash: T::AccountId, maybe_controller: Option, maybe_total: Option>, maybe_unlocking: Option>, T::MaxUnlockingChunks>>, ) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; let current_lock = T::Currency::balance_locked(crate::STAKING_ID, &stash); let stash_balance = T::Currency::free_balance(&stash); let (new_controller, new_total) = match Self::inspect_bond_state(&stash) { Ok(LedgerIntegrityState::Corrupted) => { let new_controller = maybe_controller.unwrap_or(stash.clone()); let new_total = if let Some(total) = maybe_total { let new_total = total.min(stash_balance); // enforce lock == ledger.amount. T::Currency::set_lock( crate::STAKING_ID, &stash, new_total, WithdrawReasons::all(), ); new_total } else { current_lock }; Ok((new_controller, new_total)) }, Ok(LedgerIntegrityState::CorruptedKilled) => { if current_lock == Zero::zero() { // this case needs to restore both lock and ledger, so the new total needs // to be given by the called since there's no way to restore the total // on-chain. ensure!(maybe_total.is_some(), Error::::CannotRestoreLedger); Ok(( stash.clone(), maybe_total.expect("total exists as per the check above; qed."), )) } else { Ok((stash.clone(), current_lock)) } }, Ok(LedgerIntegrityState::LockCorrupted) => { // ledger is not corrupted but its locks are out of sync. In this case, we need // to enforce a new ledger.total and staking lock for this stash. let new_total = maybe_total.ok_or(Error::::CannotRestoreLedger)?.min(stash_balance); T::Currency::set_lock( crate::STAKING_ID, &stash, new_total, WithdrawReasons::all(), ); Ok((stash.clone(), new_total)) }, Err(Error::::BadState) => { // the stash and ledger do not exist but lock is lingering. T::Currency::remove_lock(crate::STAKING_ID, &stash); ensure!( Self::inspect_bond_state(&stash) == Err(Error::::NotStash), Error::::BadState ); return Ok(()); }, Ok(LedgerIntegrityState::Ok) | Err(_) => Err(Error::::CannotRestoreLedger), }?; // re-bond stash and controller tuple. Bonded::::insert(&stash, &new_controller); // resoter ledger state. let mut ledger = StakingLedger::::new(stash.clone(), new_total); ledger.controller = Some(new_controller); ledger.unlocking = maybe_unlocking.unwrap_or_default(); ledger.update()?; ensure!( Self::inspect_bond_state(&stash) == Ok(LedgerIntegrityState::Ok), Error::::BadState ); Ok(()) } } } /// Check that list is sorted and has no duplicates. fn is_sorted_and_unique(list: &[u32]) -> bool { list.windows(2).all(|w| w[0] < w[1]) }