diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index acca7e811..69a868ba7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -55,38 +55,34 @@ jobs: - name: Run dev chain run: | timeout --preserve-status 30s ./target/release/cere --dev -# Disabled for one release to apply missing migrations -# https://github.com/Cerebellum-Network/blockchain-node/pull/300 -# - name: Check Build for Benchmarking -# run: > -# pushd node && -# cargo check --features=runtime-benchmarks --release + - name: Check Build for Benchmarking + run: > + pushd node && + cargo check --features=runtime-benchmarks --release - name: Check Build for Try-Runtime run: | cargo check --features=try-runtime --release -# Disabled for one release to apply missing migrations -# https://github.com/Cerebellum-Network/blockchain-node/pull/300 -# clippy: -# name: Run Clippy -# needs: format -# runs-on: ubuntu-22.04 -# steps: -# - uses: actions/checkout@v3 -# - name: Install linux dependencies -# run: sudo apt update && sudo apt install -y cargo clang libssl-dev llvm libudev-dev protobuf-compiler -# - name: Install nightly toolchain -# uses: actions-rs/toolchain@v1 -# with: -# toolchain: nightly-2023-05-23 -# override: true -# target: wasm32-unknown-unknown -# components: clippy -# - name: Rust Cache -# uses: Swatinem/rust-cache@v2 -# - name: Check with Clippy -# run: | -# cargo clippy --no-deps --all-targets --features runtime-benchmarks,try-runtime --workspace -- --deny warnings + clippy: + name: Run Clippy + needs: format + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: Install linux dependencies + run: sudo apt update && sudo apt install -y cargo clang libssl-dev llvm libudev-dev protobuf-compiler + - name: Install nightly toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2024-03-12 + override: true + target: wasm32-unknown-unknown + components: clippy + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + - name: Check with Clippy + run: | + cargo clippy --no-deps --all-targets --features runtime-benchmarks,try-runtime --workspace -- --deny warnings tests: name: Run tests diff --git a/Cargo.lock b/Cargo.lock index 127379e08..012bed4b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -779,13 +779,16 @@ dependencies = [ "pallet-membership", "pallet-multisig", "pallet-nomination-pools", + "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", + "pallet-offences-benchmarking", "pallet-preimage", "pallet-proxy", "pallet-recovery", "pallet-scheduler", "pallet-session", + "pallet-session-benchmarking", "pallet-staking", "pallet-staking-reward-curve", "pallet-sudo", @@ -893,13 +896,16 @@ dependencies = [ "pallet-membership", "pallet-multisig", "pallet-nomination-pools", + "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", + "pallet-offences-benchmarking", "pallet-preimage", "pallet-proxy", "pallet-recovery", "pallet-scheduler", "pallet-session", + "pallet-session-benchmarking", "pallet-staking", "pallet-staking-reward-curve", "pallet-sudo", @@ -5218,6 +5224,26 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-nomination-pools-benchmarking" +version = "1.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0#948fbd2fd1233dc26dbb9f9bbc1d2cca2c03945d" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "pallet-bags-list", + "pallet-nomination-pools", + "pallet-staking", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-runtime-interface", + "sp-staking", + "sp-std", +] + [[package]] name = "pallet-nomination-pools-runtime-api" version = "1.0.0-dev" @@ -5246,6 +5272,30 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-offences-benchmarking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0#948fbd2fd1233dc26dbb9f9bbc1d2cca2c03945d" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-babe", + "pallet-balances", + "pallet-grandpa", + "pallet-im-online", + "pallet-offences", + "pallet-session", + "pallet-staking", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-staking", + "sp-std", +] + [[package]] name = "pallet-preimage" version = "4.0.0-dev" @@ -5331,9 +5381,26 @@ dependencies = [ "sp-trie", ] +[[package]] +name = "pallet-session-benchmarking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0#948fbd2fd1233dc26dbb9f9bbc1d2cca2c03945d" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-session", + "pallet-staking", + "rand 0.8.5", + "sp-runtime", + "sp-session", + "sp-std", +] + [[package]] name = "pallet-staking" version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0#948fbd2fd1233dc26dbb9f9bbc1d2cca2c03945d" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5341,24 +5408,16 @@ dependencies = [ "frame-system", "log", "pallet-authorship", - "pallet-bags-list", - "pallet-balances", "pallet-session", - "pallet-staking-reward-curve", - "pallet-timestamp", "parity-scale-codec", "rand_chacha 0.2.2", "scale-info", "serde", "sp-application-crypto", - "sp-core", "sp-io", - "sp-npos-elections", "sp-runtime", "sp-staking", "sp-std", - "sp-tracing", - "substrate-test-utils", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f5d985fff..697d057ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,6 @@ members = [ "pallets/ddc-staking", "pallets/erc20", "pallets/erc721", - "pallets/staking", "primitives", "runtime/cere", "runtime/cere-dev", @@ -84,14 +83,14 @@ pallet-nomination-pools = { git = "https://github.com/paritytech/substrate.git", pallet-nomination-pools-benchmarking = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } pallet-nomination-pools-runtime-api = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } pallet-offences = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } +pallet-offences-benchmarking = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } pallet-preimage = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } pallet-proxy = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } pallet-recovery = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } pallet-scheduler = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } pallet-session = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false, features = ["historical"] } pallet-session-benchmarking = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } -# pallet-staking = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } -pallet-staking = { path = "pallets/staking", default-features = false } +pallet-staking = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } pallet-staking-reward-curve = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } pallet-sudo = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } pallet-timestamp = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } diff --git a/README.md b/README.md index 1072beaba..ff20ca694 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ First, complete the [basic Rust setup instructions](./docs/rust-setup.md). Use the following command to build the node without launching it: ```sh -cargo +nightly-2023-05-23 build --release +cargo +nightly-2024-03-12 build --release ``` ## Run diff --git a/pallets/staking/Cargo.toml b/pallets/staking/Cargo.toml deleted file mode 100644 index 00c245367..000000000 --- a/pallets/staking/Cargo.toml +++ /dev/null @@ -1,73 +0,0 @@ -[package] -name = "pallet-staking" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -homepage = "https://substrate.io" -license = "Apache-2.0" -readme = "README.md" -repository = "https://github.com/paritytech/substrate/" -description = "FRAME pallet staking" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -frame-election-provider-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0", default-features = false } -frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } -log = { version = "0.4.17", default-features = false } -pallet-authorship = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } -pallet-session = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false, features = ["historical"] } -scale-info = { version = "2.5.0", default-features = false, features = ["derive", "serde"] } -serde = { version = "1.0.163", default-features = false, features = ["alloc", "derive"] } -sp-application-crypto = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false, features = ["serde"] } -sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } -sp-staking = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", default-features = false } - -# Optional imports for benchmarking -frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0", default-features = false, optional = true } -rand_chacha = { version = "0.2", default-features = false, optional = true } - -[dev-dependencies] -frame-benchmarking = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } -frame-election-provider-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } -pallet-bags-list = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } -pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } -pallet-staking-reward-curve = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } -pallet-timestamp = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } -rand_chacha = { version = "0.2" } -sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } -sp-npos-elections = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } -sp-tracing = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } -substrate-test-utils = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } - -[features] -default = ["std"] -std = [ - "frame-benchmarking?/std", - "serde/std", - "codec/std", - "scale-info/std", - "sp-std/std", - "sp-io/std", - "frame-support/std", - "sp-runtime/std", - "sp-staking/std", - "pallet-session/std", - "frame-system/std", - "pallet-authorship/std", - "sp-application-crypto/std", - "log/std", - "frame-election-provider-support/std", -] -runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-election-provider-support/runtime-benchmarks", - "rand_chacha", - "sp-staking/runtime-benchmarks", -] -try-runtime = ["frame-support/try-runtime", "frame-election-provider-support/try-runtime"] diff --git a/pallets/staking/README.md b/pallets/staking/README.md deleted file mode 100644 index ccb9901a6..000000000 --- a/pallets/staking/README.md +++ /dev/null @@ -1,261 +0,0 @@ -# Staking Module - -The Staking module is used to manage funds at stake by network maintainers. - -- [`staking::Config`](https://docs.rs/pallet-staking/latest/pallet_staking/trait.Config.html) -- [`Call`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html) -- [`Module`](https://docs.rs/pallet-staking/latest/pallet_staking/struct.Module.html) - -## Overview - -The Staking module is the means by which a set of network maintainers (known as _authorities_ in -some contexts and _validators_ in others) are chosen based upon those who voluntarily place -funds under deposit. Under deposit, those funds are rewarded under normal operation but are held -at pain of _slash_ (expropriation) should the staked maintainer be found not to be discharging -its duties properly. - -### Terminology - - -- Staking: The process of locking up funds for some time, placing them at risk of slashing - (loss) in order to become a rewarded maintainer of the network. -- Validating: The process of running a node to actively maintain the network, either by - producing blocks or guaranteeing finality of the chain. -- Nominating: The process of placing staked funds behind one or more validators in order to - share in any reward, and punishment, they take. -- Stash account: The account holding an owner's funds used for staking. -- Controller account: The account that controls an owner's funds for staking. -- Era: A (whole) number of sessions, which is the period that the validator set (and each - validator's active nominator set) is recalculated and where rewards are paid out. -- Slash: The punishment of a staker by reducing its funds. - -### Goals - - -The staking system in Substrate NPoS is designed to make the following possible: - -- Stake funds that are controlled by a cold wallet. -- Withdraw some, or deposit more, funds without interrupting the role of an entity. -- Switch between roles (nominator, validator, idle) with minimal overhead. - -### Scenarios - -#### Staking - -Almost any interaction with the Staking module requires a process of _**bonding**_ (also known -as being a _staker_). To become *bonded*, a fund-holding account known as the _stash account_, -which holds some or all of the funds that become frozen in place as part of the staking process, -is paired with an active **controller** account, which issues instructions on how they shall be -used. - -An account pair can become bonded using the [`bond`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.bond) call. - -Stash accounts can update their associated controller back to their stash account using the -[`set_controller`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.set_controller) -call. - -Note: Controller accounts are being deprecated in favor of proxy accounts, so it is no longer -possible to set a unique address for a stash's controller. - -There are three possible roles that any staked account pair can be in: `Validator`, `Nominator` -and `Idle` (defined in [`StakerStatus`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.StakerStatus.html)). There are three -corresponding instructions to change between roles, namely: -[`validate`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.validate), -[`nominate`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.nominate), and [`chill`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.chill). - -#### Validating - -A **validator** takes the role of either validating blocks or ensuring their finality, -maintaining the veracity of the network. A validator should avoid both any sort of malicious -misbehavior and going offline. Bonded accounts that state interest in being a validator do NOT -get immediately chosen as a validator. Instead, they are declared as a _candidate_ and they -_might_ get elected at the _next era_ as a validator. The result of the election is determined -by nominators and their votes. - -An account can become a validator candidate via the -[`validate`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.validate) call. - -#### Nomination - -A **nominator** does not take any _direct_ role in maintaining the network, instead, it votes on -a set of validators to be elected. Once interest in nomination is stated by an account, it -takes effect at the next election round. The funds in the nominator's stash account indicate the -_weight_ of its vote. Both the rewards and any punishment that a validator earns are shared -between the validator and its nominators. This rule incentivizes the nominators to NOT vote for -the misbehaving/offline validators as much as possible, simply because the nominators will also -lose funds if they vote poorly. - -An account can become a nominator via the [`nominate`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.nominate) call. - -#### Rewards and Slash - -The **reward and slashing** procedure is the core of the Staking module, attempting to _embrace -valid behavior_ while _punishing any misbehavior or lack of availability_. - -Rewards must be claimed for each era before it gets too old by `$HISTORY_DEPTH` using the -`payout_stakers` call. Any account can call `payout_stakers`, which pays the reward to the -validator as well as its nominators. Only the [`Config::MaxNominatorRewardedPerValidator`] -biggest stakers can claim their reward. This is to limit the i/o cost to mutate storage for each -nominator's account. - -Slashing can occur at any point in time, once misbehavior is reported. Once slashing is -determined, a value is deducted from the balance of the validator and all the nominators who -voted for this validator (values are deducted from the _stash_ account of the slashed entity). - -Slashing logic is further described in the documentation of the `slashing` module. - -Similar to slashing, rewards are also shared among a validator and its associated nominators. -Yet, the reward funds are not always transferred to the stash account and can be configured. See -[Reward Calculation](https://docs.rs/pallet-staking/latest/pallet_staking/#reward-calculation) for more details. - -#### Chilling - -Finally, any of the roles above can choose to step back temporarily and just chill for a while. -This means that if they are a nominator, they will not be considered as voters anymore and if -they are validators, they will no longer be a candidate for the next election. - -An account can step back via the [`chill`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.chill) call. - -### Session managing - -The module implement the trait `SessionManager`. Which is the only API to query new validator -set and allowing these validator set to be rewarded once their era is ended. - -## Interface - -### Dispatchable Functions - -The dispatchable functions of the Staking module enable the steps needed for entities to accept -and change their role, alongside some helper functions to get/set the metadata of the module. - -### Public Functions - -The Staking module contains many public storage items and (im)mutable functions. - -## Usage - -### Example: Rewarding a validator by id. - -```rust -use pallet_staking::{self as staking}; - -#[frame_support::pallet] -pub mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::config] - pub trait Config: frame_system::Config + staking::Config {} - - #[pallet::call] - impl Pallet { - /// Reward a validator. - #[pallet::weight(0)] - pub fn reward_myself(origin: OriginFor) -> DispatchResult { - let reported = ensure_signed(origin)?; - >::reward_by_ids(vec![(reported, 10)]); - Ok(()) - } - } -} -``` - -## Implementation Details - -### Era payout - -The era payout is computed using yearly inflation curve defined at -[`T::RewardCurve`](https://docs.rs/pallet-staking/latest/pallet_staking/trait.Config.html#associatedtype.RewardCurve) as such: - -```nocompile -staker_payout = yearly_inflation(npos_token_staked / total_tokens) * total_tokens / era_per_year -``` -This payout is used to reward stakers as defined in next section - -```nocompile -remaining_payout = max_yearly_inflation * total_tokens / era_per_year - staker_payout -``` -The remaining reward is send to the configurable end-point -[`T::RewardRemainder`](https://docs.rs/pallet-staking/latest/pallet_staking/trait.Config.html#associatedtype.RewardRemainder). - -### Reward Calculation - -Validators and nominators are rewarded at the end of each era. The total reward of an era is -calculated using the era duration and the staking rate (the total amount of tokens staked by -nominators and validators, divided by the total token supply). It aims to incentivize toward a -defined staking rate. The full specification can be found -[here](https://research.web3.foundation/en/latest/polkadot/economics/1-token-economics.html#inflation-model). - -Total reward is split among validators and their nominators depending on the number of points -they received during the era. Points are added to a validator using -[`reward_by_ids`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.reward_by_ids) or -[`reward_by_indices`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.reward_by_indices). - -[`Module`](https://docs.rs/pallet-staking/latest/pallet_staking/struct.Module.html) implements -[`pallet_authorship::EventHandler`](https://docs.rs/pallet-authorship/latest/pallet_authorship/trait.EventHandler.html) to add reward -points to block producer and block producer of referenced uncles. - -The validator and its nominator split their reward as following: - -The validator can declare an amount, named -[`commission`](https://docs.rs/pallet-staking/latest/pallet_staking/struct.ValidatorPrefs.html#structfield.commission), that does not get shared -with the nominators at each reward payout through its -[`ValidatorPrefs`](https://docs.rs/pallet-staking/latest/pallet_staking/struct.ValidatorPrefs.html). This value gets deducted from the total reward -that is paid to the validator and its nominators. The remaining portion is split among the -validator and all of the nominators that nominated the validator, proportional to the value -staked behind this validator (_i.e._ dividing the -[`own`](https://docs.rs/pallet-staking/latest/pallet_staking/struct.Exposure.html#structfield.own) or -[`others`](https://docs.rs/pallet-staking/latest/pallet_staking/struct.Exposure.html#structfield.others) by -[`total`](https://docs.rs/pallet-staking/latest/pallet_staking/struct.Exposure.html#structfield.total) in [`Exposure`](https://docs.rs/pallet-staking/latest/pallet_staking/struct.Exposure.html)). - -All entities who receive a reward have the option to choose their reward destination through the -[`Payee`](https://docs.rs/pallet-staking/latest/pallet_staking/struct.Payee.html) storage item (see -[`set_payee`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.set_payee)), to be one of the following: - -- Controller account, (obviously) not increasing the staked value. -- Stash account, not increasing the staked value. -- Stash account, also increasing the staked value. - -### Additional Fund Management Operations - -Any funds already placed into stash can be the target of the following operations: - -The controller account can free a portion (or all) of the funds using the -[`unbond`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.unbond) call. Note that the funds are not immediately -accessible. Instead, a duration denoted by [`BondingDuration`](https://docs.rs/pallet-staking/latest/pallet_staking/trait.Config.html#associatedtype.BondingDuration) -(in number of eras) must pass until the funds can actually be removed. Once the -`BondingDuration` is over, the [`withdraw_unbonded`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.withdraw_unbonded) -call can be used to actually withdraw the funds. - -Note that there is a limitation to the number of fund-chunks that can be scheduled to be -unlocked in the future via [`unbond`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.unbond). In case this maximum -(`MAX_UNLOCKING_CHUNKS`) is reached, the bonded account _must_ first wait until a successful -call to `withdraw_unbonded` to remove some of the chunks. - -### Election Algorithm - -The current election algorithm is implemented based on Phragmén. The reference implementation -can be found [here](https://github.com/w3f/consensus/tree/master/NPoS). - -The election algorithm, aside from electing the validators with the most stake value and votes, -tries to divide the nominator votes among candidates in an equal manner. To further assure this, -an optional post-processing can be applied that iteratively normalizes the nominator staked -values until the total difference among votes of a particular nominator are less than a -threshold. - -## GenesisConfig - -The Staking module depends on the [`GenesisConfig`](https://docs.rs/pallet-staking/latest/pallet_staking/struct.GenesisConfig.html). The -`GenesisConfig` is optional and allow to set some initial stakers. - -## Related Modules - -- [Balances](https://docs.rs/pallet-balances/latest/pallet_balances/): Used to manage values at stake. -- [Session](https://docs.rs/pallet-session/latest/pallet_session/): Used to manage sessions. Also, a list of new - validators is stored in the Session module's `Validators` at the end of each era. - -License: Apache-2.0 diff --git a/pallets/staking/src/benchmarking.rs b/pallets/staking/src/benchmarking.rs deleted file mode 100644 index 6ef782012..000000000 --- a/pallets/staking/src/benchmarking.rs +++ /dev/null @@ -1,1066 +0,0 @@ -// 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 pallet benchmarking. - -use codec::Decode; -pub use frame_benchmarking::v1::{ - account, benchmarks, impl_benchmark_test_suite, whitelist_account, whitelisted_caller, -}; -use frame_election_provider_support::SortedListProvider; -use frame_support::{ - dispatch::UnfilteredDispatchable, - pallet_prelude::*, - traits::{Currency, Get, Imbalance}, -}; -use frame_system::RawOrigin; -use sp_runtime::{ - traits::{Bounded, One, StaticLookup, TrailingZeroInput, Zero}, - Perbill, Percent, -}; -use sp_staking::{currency_to_vote::CurrencyToVote, SessionIndex}; -use sp_std::prelude::*; -use testing_utils::*; - -use super::*; -use crate::{ConfigOp, Pallet as Staking}; - -const SEED: u32 = 0; -const MAX_SPANS: u32 = 100; -const MAX_SLASHES: u32 = 1000; - -type MaxValidators = <::BenchmarkingConfig as BenchmarkingConfig>::MaxValidators; -type MaxNominators = <::BenchmarkingConfig as BenchmarkingConfig>::MaxNominators; - -// Add slashing spans to a user account. Not relevant for actual use, only to benchmark -// read and write operations. -pub fn add_slashing_spans(who: &T::AccountId, spans: u32) { - if spans == 0 { - return - } - - // For the first slashing span, we initialize - let mut slashing_spans = crate::slashing::SlashingSpans::new(0); - SpanSlash::::insert((who, 0), crate::slashing::SpanRecord::default()); - - for i in 1..spans { - assert!(slashing_spans.end_span(i)); - SpanSlash::::insert((who, i), crate::slashing::SpanRecord::default()); - } - SlashingSpans::::insert(who, slashing_spans); -} - -// This function clears all existing validators and nominators from the set, and generates one new -// validator being nominated by n nominators, and returns the validator stash account and the -// nominators' stash and controller. It also starts an era and creates pending payouts. -pub fn create_validator_with_nominators( - n: u32, - upper_bound: u32, - dead_controller: bool, - unique_controller: bool, - destination: RewardDestination, -) -> Result<(T::AccountId, Vec<(T::AccountId, T::AccountId)>), &'static str> { - // Clean up any existing state. - clear_validators_and_nominators::(); - let mut points_total = 0; - let mut points_individual = Vec::new(); - - let (v_stash, v_controller) = if unique_controller { - create_unique_stash_controller::(0, 100, destination.clone(), false)? - } else { - create_stash_controller::(0, 100, destination.clone())? - }; - - let validator_prefs = - ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; - Staking::::validate(RawOrigin::Signed(v_controller).into(), validator_prefs)?; - let stash_lookup = T::Lookup::unlookup(v_stash.clone()); - - points_total += 10; - points_individual.push((v_stash.clone(), 10)); - - let original_nominator_count = Nominators::::count(); - let mut nominators = Vec::new(); - - // Give the validator n nominators, but keep total users in the system the same. - for i in 0..upper_bound { - let (n_stash, n_controller) = if !dead_controller { - create_stash_controller::(u32::MAX - i, 100, destination.clone())? - } else { - create_unique_stash_controller::(u32::MAX - i, 100, destination.clone(), true)? - }; - if i < n { - Staking::::nominate( - RawOrigin::Signed(n_controller.clone()).into(), - vec![stash_lookup.clone()], - )?; - nominators.push((n_stash, n_controller)); - } - } - - ValidatorCount::::put(1); - - // Start a new Era - let new_validators = Staking::::try_trigger_new_era(SessionIndex::one(), true).unwrap(); - - assert_eq!(new_validators.len(), 1); - assert_eq!(new_validators[0], v_stash, "Our validator was not selected!"); - assert_ne!(Validators::::count(), 0); - assert_eq!(Nominators::::count(), original_nominator_count + nominators.len() as u32); - - // Give Era Points - let reward = EraRewardPoints:: { - total: points_total, - individual: points_individual.into_iter().collect(), - }; - - let current_era = CurrentEra::::get().unwrap(); - ErasRewardPoints::::insert(current_era, reward); - - // Create reward pool - let total_payout = T::Currency::minimum_balance() - .saturating_mul(upper_bound.into()) - .saturating_mul(1000u32.into()); - >::insert(current_era, total_payout); - - Ok((v_stash, nominators)) -} - -struct ListScenario { - /// Stash that is expected to be moved. - origin_stash1: T::AccountId, - /// Controller of the Stash that is expected to be moved. - origin_controller1: T::AccountId, - dest_weight: BalanceOf, -} - -impl ListScenario { - /// An expensive scenario for bags-list implementation: - /// - /// - the node to be updated (r) is the head of a bag that has at least one other node. The bag - /// itself will need to be read and written to update its head. The node pointed to by r.next - /// will need to be read and written as it will need to have its prev pointer updated. Note - /// that there are two other worst case scenarios for bag removal: 1) the node is a tail and - /// 2) the node is a middle node with prev and next; all scenarios end up with the same number - /// of storage reads and writes. - /// - /// - the destination bag has at least one node, which will need its next pointer updated. - /// - /// NOTE: while this scenario specifically targets a worst case for the bags-list, it should - /// also elicit a worst case for other known `VoterList` implementations; although - /// this may not be true against unknown `VoterList` implementations. - fn new(origin_weight: BalanceOf, is_increase: bool) -> Result { - ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); - - // burn the entire issuance. - let i = T::Currency::burn(T::Currency::total_issuance()); - sp_std::mem::forget(i); - - // create accounts with the origin weight - - let (origin_stash1, origin_controller1) = create_stash_controller_with_balance::( - USER_SEED + 2, - origin_weight, - Default::default(), - )?; - Staking::::nominate( - RawOrigin::Signed(origin_controller1.clone()).into(), - // NOTE: these don't really need to be validators. - vec![T::Lookup::unlookup(account("random_validator", 0, SEED))], - )?; - - let (_origin_stash2, origin_controller2) = create_stash_controller_with_balance::( - USER_SEED + 3, - origin_weight, - Default::default(), - )?; - Staking::::nominate( - RawOrigin::Signed(origin_controller2).into(), - vec![T::Lookup::unlookup(account("random_validator", 0, SEED))], - )?; - - // find a destination weight that will trigger the worst case scenario - let dest_weight_as_vote = - T::VoterList::score_update_worst_case(&origin_stash1, is_increase); - - let total_issuance = T::Currency::total_issuance(); - - let dest_weight = - T::CurrencyToVote::to_currency(dest_weight_as_vote as u128, total_issuance); - - // create an account with the worst case destination weight - let (_dest_stash1, dest_controller1) = create_stash_controller_with_balance::( - USER_SEED + 1, - dest_weight, - Default::default(), - )?; - Staking::::nominate( - RawOrigin::Signed(dest_controller1).into(), - vec![T::Lookup::unlookup(account("random_validator", 0, SEED))], - )?; - - Ok(ListScenario { origin_stash1, origin_controller1, dest_weight }) - } -} - -const USER_SEED: u32 = 999666; - -benchmarks! { - bond { - let stash = create_funded_user::("stash", USER_SEED, 100); - let reward_destination = RewardDestination::Staked; - let amount = T::Currency::minimum_balance() * 10u32.into(); - whitelist_account!(stash); - }: _(RawOrigin::Signed(stash.clone()), amount, reward_destination) - verify { - assert!(Bonded::::contains_key(stash.clone())); - assert!(Ledger::::contains_key(stash)); - } - - bond_extra { - // clean up any existing state. - clear_validators_and_nominators::(); - - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); - - // setup the worst case list scenario. - - // the weight the nominator will start at. - let scenario = ListScenario::::new(origin_weight, true)?; - - let max_additional = scenario.dest_weight - origin_weight; - - let stash = scenario.origin_stash1.clone(); - let controller = scenario.origin_controller1; - let original_bonded: BalanceOf - = Ledger::::get(&controller).map(|l| l.active).ok_or("ledger not created after")?; - - T::Currency::deposit_into_existing(&stash, max_additional).unwrap(); - - whitelist_account!(stash); - }: _(RawOrigin::Signed(stash), max_additional) - verify { - let ledger = Ledger::::get(&controller).ok_or("ledger not created after")?; - let new_bonded: BalanceOf = ledger.active; - assert!(original_bonded < new_bonded); - } - - unbond { - // clean up any existing state. - clear_validators_and_nominators::(); - - // setup the worst case list scenario. - let total_issuance = T::Currency::total_issuance(); - // the weight the nominator will start at. The value used here is expected to be - // significantly higher than the first position in a list (e.g. the first bag threshold). - let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) - .map_err(|_| "balance expected to be a u128") - .unwrap(); - let scenario = ListScenario::::new(origin_weight, false)?; - - let stash = scenario.origin_stash1.clone(); - let controller = scenario.origin_controller1.clone(); - let amount = origin_weight - scenario.dest_weight; - let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; - let original_bonded: BalanceOf = ledger.active; - - whitelist_account!(controller); - }: _(RawOrigin::Signed(controller.clone()), amount) - verify { - let ledger = Ledger::::get(&controller).ok_or("ledger not created after")?; - let new_bonded: BalanceOf = ledger.active; - assert!(original_bonded > new_bonded); - } - - // Withdraw only updates the ledger - withdraw_unbonded_update { - // Slashing Spans - let s in 0 .. MAX_SPANS; - let (stash, controller) = create_stash_controller::(0, 100, Default::default())?; - add_slashing_spans::(&stash, s); - let amount = T::Currency::minimum_balance() * 5u32.into(); // Half of total - Staking::::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?; - CurrentEra::::put(EraIndex::max_value()); - let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; - let original_total: BalanceOf = ledger.total; - whitelist_account!(controller); - }: withdraw_unbonded(RawOrigin::Signed(controller.clone()), s) - verify { - let ledger = Ledger::::get(&controller).ok_or("ledger not created after")?; - let new_total: BalanceOf = ledger.total; - assert!(original_total > new_total); - } - - // Worst case scenario, everything is removed after the bonding duration - withdraw_unbonded_kill { - // Slashing Spans - let s in 0 .. MAX_SPANS; - // clean up any existing state. - clear_validators_and_nominators::(); - - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); - - // setup a worst case list scenario. Note that we don't care about the setup of the - // destination position because we are doing a removal from the list but no insert. - let scenario = ListScenario::::new(origin_weight, true)?; - let controller = scenario.origin_controller1.clone(); - let stash = scenario.origin_stash1; - add_slashing_spans::(&stash, s); - assert!(T::VoterList::contains(&stash)); - - let ed = T::Currency::minimum_balance(); - let mut ledger = Ledger::::get(&controller).unwrap(); - ledger.active = ed - One::one(); - Ledger::::insert(&controller, ledger); - CurrentEra::::put(EraIndex::max_value()); - - whitelist_account!(controller); - }: withdraw_unbonded(RawOrigin::Signed(controller.clone()), s) - verify { - assert!(!Ledger::::contains_key(controller)); - assert!(!T::VoterList::contains(&stash)); - } - - validate { - let (stash, controller) = create_stash_controller::( - T::MaxNominations::get() - 1, - 100, - Default::default(), - )?; - // because it is chilled. - assert!(!T::VoterList::contains(&stash)); - - let prefs = ValidatorPrefs::default(); - whitelist_account!(controller); - }: _(RawOrigin::Signed(controller), prefs) - verify { - assert!(Validators::::contains_key(&stash)); - assert!(T::VoterList::contains(&stash)); - } - - kick { - // scenario: we want to kick `k` nominators from nominating us (we are a validator). - // we'll assume that `k` is under 128 for the purposes of determining the slope. - // each nominator should have `T::MaxNominations::get()` validators nominated, and our validator - // should be somewhere in there. - let k in 1 .. 128; - - // these are the other validators; there are `T::MaxNominations::get() - 1` of them, so - // there are a total of `T::MaxNominations::get()` validators in the system. - let rest_of_validators = create_validators_with_seed::(T::MaxNominations::get() - 1, 100, 415)?; - - // this is the validator that will be kicking. - let (stash, controller) = create_stash_controller::( - T::MaxNominations::get() - 1, - 100, - Default::default(), - )?; - let stash_lookup = T::Lookup::unlookup(stash.clone()); - - // they start validating. - Staking::::validate(RawOrigin::Signed(controller.clone()).into(), Default::default())?; - - // we now create the nominators. there will be `k` of them; each will nominate all - // validators. we will then kick each of the `k` nominators from the main validator. - let mut nominator_stashes = Vec::with_capacity(k as usize); - for i in 0 .. k { - // create a nominator stash. - let (n_stash, n_controller) = create_stash_controller::( - T::MaxNominations::get() + i, - 100, - Default::default(), - )?; - - // bake the nominations; we first clone them from the rest of the validators. - let mut nominations = rest_of_validators.clone(); - // then insert "our" validator somewhere in there (we vary it) to avoid accidental - // optimisations/pessimisations. - nominations.insert(i as usize % (nominations.len() + 1), stash_lookup.clone()); - // then we nominate. - Staking::::nominate(RawOrigin::Signed(n_controller.clone()).into(), nominations)?; - - nominator_stashes.push(n_stash); - } - - // all nominators now should be nominating our validator... - for n in nominator_stashes.iter() { - assert!(Nominators::::get(n).unwrap().targets.contains(&stash)); - } - - // we need the unlookuped version of the nominator stash for the kick. - let kicks = nominator_stashes.iter() - .map(|n| T::Lookup::unlookup(n.clone())) - .collect::>(); - - whitelist_account!(controller); - }: _(RawOrigin::Signed(controller), kicks) - verify { - // all nominators now should *not* be nominating our validator... - for n in nominator_stashes.iter() { - assert!(!Nominators::::get(n).unwrap().targets.contains(&stash)); - } - } - - // Worst case scenario, T::MaxNominations::get() - nominate { - let n in 1 .. T::MaxNominations::get(); - - // clean up any existing state. - clear_validators_and_nominators::(); - - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); - - // setup a worst case list scenario. Note we don't care about the destination position, because - // we are just doing an insert into the origin position. - let scenario = ListScenario::::new(origin_weight, true)?; - let (stash, controller) = create_stash_controller_with_balance::( - SEED + T::MaxNominations::get() + 1, // make sure the account does not conflict with others - origin_weight, - Default::default(), - ).unwrap(); - - assert!(!Nominators::::contains_key(&stash)); - assert!(!T::VoterList::contains(&stash)); - - let validators = create_validators::(n, 100).unwrap(); - whitelist_account!(controller); - }: _(RawOrigin::Signed(controller), validators) - verify { - assert!(Nominators::::contains_key(&stash)); - assert!(T::VoterList::contains(&stash)) - } - - chill { - // clean up any existing state. - clear_validators_and_nominators::(); - - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); - - // setup a worst case list scenario. Note that we don't care about the setup of the - // destination position because we are doing a removal from the list but no insert. - let scenario = ListScenario::::new(origin_weight, true)?; - let controller = scenario.origin_controller1.clone(); - let stash = scenario.origin_stash1; - assert!(T::VoterList::contains(&stash)); - - whitelist_account!(controller); - }: _(RawOrigin::Signed(controller)) - verify { - assert!(!T::VoterList::contains(&stash)); - } - - set_payee { - let (stash, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; - assert_eq!(Payee::::get(&stash), RewardDestination::Staked); - whitelist_account!(controller); - }: _(RawOrigin::Signed(controller), RewardDestination::Controller) - verify { - assert_eq!(Payee::::get(&stash), RewardDestination::Controller); - } - - set_controller { - let (stash, ctlr) = create_unique_stash_controller::(9000, 100, Default::default(), false)?; - // ensure `ctlr` is the currently stored controller. - assert!(!Ledger::::contains_key(&stash)); - assert!(Ledger::::contains_key(&ctlr)); - assert_eq!(Bonded::::get(&stash), Some(ctlr.clone())); - - whitelist_account!(stash); - }: _(RawOrigin::Signed(stash.clone())) - verify { - assert!(Ledger::::contains_key(&stash)); - } - - set_validator_count { - let validator_count = MaxValidators::::get(); - }: _(RawOrigin::Root, validator_count) - verify { - assert_eq!(ValidatorCount::::get(), validator_count); - } - - force_no_eras {}: _(RawOrigin::Root) - verify { assert_eq!(ForceEra::::get(), Forcing::ForceNone); } - - force_new_era {}: _(RawOrigin::Root) - verify { assert_eq!(ForceEra::::get(), Forcing::ForceNew); } - - force_new_era_always {}: _(RawOrigin::Root) - verify { assert_eq!(ForceEra::::get(), Forcing::ForceAlways); } - - // Worst case scenario, the list of invulnerables is very long. - set_invulnerables { - let v in 0 .. MaxValidators::::get(); - let mut invulnerables = Vec::new(); - for i in 0 .. v { - invulnerables.push(account("invulnerable", i, SEED)); - } - }: _(RawOrigin::Root, invulnerables) - verify { - assert_eq!(Invulnerables::::get().len(), v as usize); - } - - force_unstake { - // Slashing Spans - let s in 0 .. MAX_SPANS; - // Clean up any existing state. - clear_validators_and_nominators::(); - - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); - - // setup a worst case list scenario. Note that we don't care about the setup of the - // destination position because we are doing a removal from the list but no insert. - let scenario = ListScenario::::new(origin_weight, true)?; - let controller = scenario.origin_controller1.clone(); - let stash = scenario.origin_stash1; - assert!(T::VoterList::contains(&stash)); - add_slashing_spans::(&stash, s); - - }: _(RawOrigin::Root, stash.clone(), s) - verify { - assert!(!Ledger::::contains_key(&controller)); - assert!(!T::VoterList::contains(&stash)); - } - - cancel_deferred_slash { - let s in 1 .. MAX_SLASHES; - let mut unapplied_slashes = Vec::new(); - let era = EraIndex::one(); - let dummy = || T::AccountId::decode(&mut TrailingZeroInput::zeroes()).unwrap(); - for _ in 0 .. MAX_SLASHES { - unapplied_slashes.push(UnappliedSlash::>::default_from(dummy())); - } - UnappliedSlashes::::insert(era, &unapplied_slashes); - - let slash_indices: Vec = (0 .. s).collect(); - }: _(RawOrigin::Root, era, slash_indices) - verify { - assert_eq!(UnappliedSlashes::::get(&era).len(), (MAX_SLASHES - s) as usize); - } - - payout_stakers_dead_controller { - let n in 0 .. T::MaxNominatorRewardedPerValidator::get() as u32; - let (validator, nominators) = create_validator_with_nominators::( - n, - T::MaxNominatorRewardedPerValidator::get() as u32, - true, - true, - RewardDestination::Controller, - )?; - - let current_era = CurrentEra::::get().unwrap(); - // set the commission for this particular era as well. - >::insert(current_era, validator.clone(), >::validators(&validator)); - - let caller = whitelisted_caller(); - let validator_controller = >::get(&validator).unwrap(); - let balance_before = T::Currency::free_balance(&validator_controller); - for (_, controller) in &nominators { - let balance = T::Currency::free_balance(controller); - ensure!(balance.is_zero(), "Controller has balance, but should be dead."); - } - }: payout_stakers(RawOrigin::Signed(caller), validator, current_era) - verify { - let balance_after = T::Currency::free_balance(&validator_controller); - ensure!( - balance_before < balance_after, - "Balance of validator controller should have increased after payout.", - ); - for (_, controller) in &nominators { - let balance = T::Currency::free_balance(controller); - ensure!(!balance.is_zero(), "Payout not given to controller."); - } - } - - payout_stakers_alive_staked { - let n in 0 .. T::MaxNominatorRewardedPerValidator::get() as u32; - let (validator, nominators) = create_validator_with_nominators::( - n, - T::MaxNominatorRewardedPerValidator::get() as u32, - false, - true, - RewardDestination::Staked, - )?; - - let current_era = CurrentEra::::get().unwrap(); - // set the commission for this particular era as well. - >::insert(current_era, validator.clone(), >::validators(&validator)); - - let caller = whitelisted_caller(); - let balance_before = T::Currency::free_balance(&validator); - let mut nominator_balances_before = Vec::new(); - for (stash, _) in &nominators { - let balance = T::Currency::free_balance(stash); - nominator_balances_before.push(balance); - } - }: payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era) - verify { - let balance_after = T::Currency::free_balance(&validator); - ensure!( - balance_before < balance_after, - "Balance of validator stash should have increased after payout.", - ); - for ((stash, _), balance_before) in nominators.iter().zip(nominator_balances_before.iter()) { - let balance_after = T::Currency::free_balance(stash); - ensure!( - balance_before < &balance_after, - "Balance of nominator stash should have increased after payout.", - ); - } - } - - rebond { - let l in 1 .. T::MaxUnlockingChunks::get() as u32; - - // clean up any existing state. - clear_validators_and_nominators::(); - - let origin_weight = MinNominatorBond::::get() - .max(T::Currency::minimum_balance()) - // we use 100 to play friendly with the list threshold values in the mock - .max(100u32.into()); - - // setup a worst case list scenario. - let scenario = ListScenario::::new(origin_weight, true)?; - let dest_weight = scenario.dest_weight; - - // rebond an amount that will give the user dest_weight - let rebond_amount = dest_weight - origin_weight; - - // spread that amount to rebond across `l` unlocking chunks, - let value = rebond_amount / l.into(); - // if `value` is zero, we need a greater delta between dest <=> origin weight - assert_ne!(value, Zero::zero()); - // so the sum of unlocking chunks puts voter into the dest bag. - assert!(value * l.into() + origin_weight > origin_weight); - assert!(value * l.into() + origin_weight <= dest_weight); - let unlock_chunk = UnlockChunk::> { - value, - era: EraIndex::zero(), - }; - - let stash = scenario.origin_stash1.clone(); - let controller = scenario.origin_controller1; - let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); - - for _ in 0 .. l { - staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap() - } - Ledger::::insert(controller.clone(), staking_ledger.clone()); - let original_bonded: BalanceOf = staking_ledger.active; - - whitelist_account!(controller); - }: _(RawOrigin::Signed(controller.clone()), rebond_amount) - verify { - let ledger = Ledger::::get(&controller).ok_or("ledger not created after")?; - let new_bonded: BalanceOf = ledger.active; - assert!(original_bonded < new_bonded); - } - - reap_stash { - let s in 1 .. MAX_SPANS; - // clean up any existing state. - clear_validators_and_nominators::(); - - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); - - // setup a worst case list scenario. Note that we don't care about the setup of the - // destination position because we are doing a removal from the list but no insert. - let scenario = ListScenario::::new(origin_weight, true)?; - let controller = scenario.origin_controller1.clone(); - let stash = scenario.origin_stash1; - - add_slashing_spans::(&stash, s); - let l = StakingLedger { - stash: stash.clone(), - active: T::Currency::minimum_balance() - One::one(), - total: T::Currency::minimum_balance() - One::one(), - unlocking: Default::default(), - claimed_rewards: Default::default(), - }; - Ledger::::insert(&controller, l); - - assert!(Bonded::::contains_key(&stash)); - assert!(T::VoterList::contains(&stash)); - - whitelist_account!(controller); - }: _(RawOrigin::Signed(controller), stash.clone(), s) - verify { - assert!(!Bonded::::contains_key(&stash)); - assert!(!T::VoterList::contains(&stash)); - } - - new_era { - let v in 1 .. 10; - let n in 0 .. 100; - - create_validators_with_nominators_for_era::( - v, - n, - ::MaxNominations::get() as usize, - false, - None, - )?; - let session_index = SessionIndex::one(); - }: { - let validators = Staking::::try_trigger_new_era(session_index, true) - .ok_or("`new_era` failed")?; - assert!(validators.len() == v as usize); - } - - #[extra] - payout_all { - let v in 1 .. 10; - let n in 0 .. 100; - create_validators_with_nominators_for_era::( - v, - n, - ::MaxNominations::get() as usize, - false, - None, - )?; - // Start a new Era - let new_validators = Staking::::try_trigger_new_era(SessionIndex::one(), true).unwrap(); - assert!(new_validators.len() == v as usize); - - let current_era = CurrentEra::::get().unwrap(); - let mut points_total = 0; - let mut points_individual = Vec::new(); - let mut payout_calls_arg = Vec::new(); - - for validator in new_validators.iter() { - points_total += 10; - points_individual.push((validator.clone(), 10)); - payout_calls_arg.push((validator.clone(), current_era)); - } - - // Give Era Points - let reward = EraRewardPoints:: { - total: points_total, - individual: points_individual.into_iter().collect(), - }; - - ErasRewardPoints::::insert(current_era, reward); - - // Create reward pool - let total_payout = T::Currency::minimum_balance() * 1000u32.into(); - >::insert(current_era, total_payout); - - let caller: T::AccountId = whitelisted_caller(); - let origin = RawOrigin::Signed(caller); - let calls: Vec<_> = payout_calls_arg.iter().map(|arg| - Call::::payout_stakers { validator_stash: arg.0.clone(), era: arg.1 }.encode() - ).collect(); - }: { - for call in calls { - as Decode>::decode(&mut &*call) - .expect("call is encoded above, encoding must be correct") - .dispatch_bypass_filter(origin.clone().into())?; - } - } - - #[extra] - do_slash { - let l in 1 .. T::MaxUnlockingChunks::get() as u32; - let (stash, controller) = create_stash_controller::(0, 100, Default::default())?; - let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); - let unlock_chunk = UnlockChunk::> { - value: 1u32.into(), - era: EraIndex::zero(), - }; - for _ in 0 .. l { - staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap(); - } - Ledger::::insert(controller, staking_ledger); - let slash_amount = T::Currency::minimum_balance() * 10u32.into(); - let balance_before = T::Currency::free_balance(&stash); - }: { - crate::slashing::do_slash::( - &stash, - slash_amount, - &mut BalanceOf::::zero(), - &mut NegativeImbalanceOf::::zero(), - EraIndex::zero() - ); - } verify { - let balance_after = T::Currency::free_balance(&stash); - assert!(balance_before > balance_after); - } - - get_npos_voters { - // number of validator intention. we will iterate all of them. - let v in (MaxValidators::::get() / 2) .. MaxValidators::::get(); - // number of nominator intention. we will iterate all of them. - let n in (MaxNominators::::get() / 2) .. MaxNominators::::get(); - - let validators = create_validators_with_nominators_for_era::( - v, n, T::MaxNominations::get() as usize, false, None - )? - .into_iter() - .map(|v| T::Lookup::lookup(v).unwrap()) - .collect::>(); - - assert_eq!(Validators::::count(), v); - assert_eq!(Nominators::::count(), n); - - let num_voters = (v + n) as usize; - }: { - let voters = >::get_npos_voters(None); - assert_eq!(voters.len(), num_voters); - } - - get_npos_targets { - // number of validator intention. - let v in (MaxValidators::::get() / 2) .. MaxValidators::::get(); - // number of nominator intention. - let n = MaxNominators::::get(); - - let _ = create_validators_with_nominators_for_era::( - v, n, T::MaxNominations::get() as usize, false, None - )?; - }: { - let targets = >::get_npos_targets(None); - assert_eq!(targets.len() as u32, v); - } - - set_staking_configs_all_set { - }: set_staking_configs( - RawOrigin::Root, - ConfigOp::Set(BalanceOf::::max_value()), - ConfigOp::Set(BalanceOf::::max_value()), - ConfigOp::Set(u32::MAX), - ConfigOp::Set(u32::MAX), - ConfigOp::Set(Percent::max_value()), - ConfigOp::Set(Perbill::max_value()) - ) verify { - assert_eq!(MinNominatorBond::::get(), BalanceOf::::max_value()); - assert_eq!(MinValidatorBond::::get(), BalanceOf::::max_value()); - assert_eq!(MaxNominatorsCount::::get(), Some(u32::MAX)); - assert_eq!(MaxValidatorsCount::::get(), Some(u32::MAX)); - assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(100))); - assert_eq!(MinCommission::::get(), Perbill::from_percent(100)); - } - - set_staking_configs_all_remove { - }: set_staking_configs( - RawOrigin::Root, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove - ) verify { - assert!(!MinNominatorBond::::exists()); - assert!(!MinValidatorBond::::exists()); - assert!(!MaxNominatorsCount::::exists()); - assert!(!MaxValidatorsCount::::exists()); - assert!(!ChillThreshold::::exists()); - assert!(!MinCommission::::exists()); - } - - chill_other { - // clean up any existing state. - clear_validators_and_nominators::(); - - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); - - // setup a worst case list scenario. Note that we don't care about the setup of the - // destination position because we are doing a removal from the list but no insert. - let scenario = ListScenario::::new(origin_weight, true)?; - let controller = scenario.origin_controller1.clone(); - let stash = scenario.origin_stash1; - assert!(T::VoterList::contains(&stash)); - - Staking::::set_staking_configs( - RawOrigin::Root.into(), - ConfigOp::Set(BalanceOf::::max_value()), - ConfigOp::Set(BalanceOf::::max_value()), - ConfigOp::Set(0), - ConfigOp::Set(0), - ConfigOp::Set(Percent::from_percent(0)), - ConfigOp::Set(Zero::zero()), - )?; - - let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), controller) - verify { - assert!(!T::VoterList::contains(&stash)); - } - - force_apply_min_commission { - // Clean up any existing state - clear_validators_and_nominators::(); - - // Create a validator with a commission of 50% - let (stash, controller) = - create_stash_controller::(1, 1, RewardDestination::Staked)?; - let validator_prefs = - ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; - Staking::::validate(RawOrigin::Signed(controller).into(), validator_prefs)?; - - // Sanity check - assert_eq!( - Validators::::get(&stash), - ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() } - ); - - // Set the min commission to 75% - MinCommission::::set(Perbill::from_percent(75)); - let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), stash.clone()) - verify { - // The validators commission has been bumped to 75% - assert_eq!( - Validators::::get(&stash), - ValidatorPrefs { commission: Perbill::from_percent(75), ..Default::default() } - ); - } - - set_min_commission { - let min_commission = Perbill::max_value(); - }: _(RawOrigin::Root, min_commission) - verify { - assert_eq!(MinCommission::::get(), Perbill::from_percent(100)); - } - - impl_benchmark_test_suite!( - Staking, - crate::mock::ExtBuilder::default().has_stakers(true), - crate::mock::Test, - exec_name = build_and_execute - ); -} - -#[cfg(test)] -mod tests { - use frame_support::assert_ok; - - use super::*; - use crate::mock::{Balances, ExtBuilder, RuntimeOrigin, Staking, Test}; - - #[test] - fn create_validators_with_nominators_for_era_works() { - ExtBuilder::default().build_and_execute(|| { - let v = 10; - let n = 100; - - create_validators_with_nominators_for_era::( - v, - n, - ::MaxNominations::get() as usize, - false, - None, - ) - .unwrap(); - - let count_validators = Validators::::iter().count(); - let count_nominators = Nominators::::iter().count(); - - assert_eq!(count_validators, Validators::::count() as usize); - assert_eq!(count_nominators, Nominators::::count() as usize); - - assert_eq!(count_validators, v as usize); - assert_eq!(count_nominators, n as usize); - }); - } - - #[test] - fn create_validator_with_nominators_works() { - ExtBuilder::default().build_and_execute(|| { - let n = 10; - - let (validator_stash, nominators) = create_validator_with_nominators::( - n, - <::MaxNominatorRewardedPerValidator as Get<_>>::get(), - false, - false, - RewardDestination::Staked, - ) - .unwrap(); - - assert_eq!(nominators.len() as u32, n); - - let current_era = CurrentEra::::get().unwrap(); - - let original_free_balance = Balances::free_balance(&validator_stash); - assert_ok!(Staking::payout_stakers( - RuntimeOrigin::signed(1337), - validator_stash, - current_era - )); - let new_free_balance = Balances::free_balance(&validator_stash); - - assert!(original_free_balance < new_free_balance); - }); - } - - #[test] - fn add_slashing_spans_works() { - ExtBuilder::default().build_and_execute(|| { - let n = 10; - - let (validator_stash, _nominators) = create_validator_with_nominators::( - n, - <::MaxNominatorRewardedPerValidator as Get<_>>::get(), - false, - false, - RewardDestination::Staked, - ) - .unwrap(); - - // Add 20 slashing spans - let num_of_slashing_spans = 20; - add_slashing_spans::(&validator_stash, num_of_slashing_spans); - - let slashing_spans = SlashingSpans::::get(&validator_stash).unwrap(); - assert_eq!(slashing_spans.iter().count(), num_of_slashing_spans as usize); - for i in 0..num_of_slashing_spans { - assert!(SpanSlash::::contains_key((&validator_stash, i))); - } - - // Test everything is cleaned up - assert_ok!(Staking::kill_stash(&validator_stash, num_of_slashing_spans)); - assert!(SlashingSpans::::get(&validator_stash).is_none()); - for i in 0..num_of_slashing_spans { - assert!(!SpanSlash::::contains_key((&validator_stash, i))); - } - }); - } - - #[test] - fn test_payout_all() { - ExtBuilder::default().build_and_execute(|| { - let v = 10; - let n = 100; - - let selected_benchmark = SelectedBenchmark::payout_all; - let c = vec![ - (frame_benchmarking::BenchmarkParameter::v, v), - (frame_benchmarking::BenchmarkParameter::n, n), - ]; - let closure_to_benchmark = - >::instance( - &selected_benchmark, - &c, - true, - ) - .unwrap(); - - assert_ok!(closure_to_benchmark()); - }); - } -} diff --git a/pallets/staking/src/inflation.rs b/pallets/staking/src/inflation.rs deleted file mode 100644 index 8b4a85b6c..000000000 --- a/pallets/staking/src/inflation.rs +++ /dev/null @@ -1,108 +0,0 @@ -// 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. - -//! This module expose one function `P_NPoS` (Payout NPoS) or `compute_total_payout` which returns -//! the total payout for the era given the era duration and the staking rate in NPoS. -//! The staking rate in NPoS is the total amount of tokens staked by nominators and validators, -//! divided by the total token supply. - -use sp_runtime::{curve::PiecewiseLinear, traits::AtLeast32BitUnsigned, Perbill}; - -/// The total payout to all validators (and their nominators) per era and maximum payout. -/// -/// Defined as such: -/// `staker-payout = yearly_inflation(npos_token_staked / total_tokens) * total_tokens / -/// era_per_year` `maximum-payout = max_yearly_inflation * total_tokens / era_per_year` -/// -/// `era_duration` is expressed in millisecond. -pub fn compute_total_payout( - yearly_inflation: &PiecewiseLinear<'static>, - npos_token_staked: N, - total_tokens: N, - era_duration: u64, -) -> (N, N) -where - N: AtLeast32BitUnsigned + Clone, -{ - // Milliseconds per year for the Julian year (365.25 days). - const MILLISECONDS_PER_YEAR: u64 = 1000 * 3600 * 24 * 36525 / 100; - - let portion = Perbill::from_rational(era_duration as u64, MILLISECONDS_PER_YEAR); - let payout = portion * - yearly_inflation - .calculate_for_fraction_times_denominator(npos_token_staked, total_tokens.clone()); - let maximum = portion * (yearly_inflation.maximum * total_tokens); - (payout, maximum) -} - -#[cfg(test)] -mod test { - use sp_runtime::curve::PiecewiseLinear; - - pallet_staking_reward_curve::build! { - const I_NPOS: PiecewiseLinear<'static> = curve!( - min_inflation: 0_025_000, - max_inflation: 0_100_000, - ideal_stake: 0_500_000, - falloff: 0_050_000, - max_piece_count: 40, - test_precision: 0_005_000, - ); - } - - #[test] - fn npos_curve_is_sensible() { - const YEAR: u64 = 365 * 24 * 60 * 60 * 1000; - - // check maximum inflation. - // not 10_000 due to rounding error. - assert_eq!(super::compute_total_payout(&I_NPOS, 0, 100_000u64, YEAR).1, 9_993); - - // super::I_NPOS.calculate_for_fraction_times_denominator(25, 100) - assert_eq!(super::compute_total_payout(&I_NPOS, 0, 100_000u64, YEAR).0, 2_498); - assert_eq!(super::compute_total_payout(&I_NPOS, 5_000, 100_000u64, YEAR).0, 3_248); - assert_eq!(super::compute_total_payout(&I_NPOS, 25_000, 100_000u64, YEAR).0, 6_246); - assert_eq!(super::compute_total_payout(&I_NPOS, 40_000, 100_000u64, YEAR).0, 8_494); - assert_eq!(super::compute_total_payout(&I_NPOS, 50_000, 100_000u64, YEAR).0, 9_993); - assert_eq!(super::compute_total_payout(&I_NPOS, 60_000, 100_000u64, YEAR).0, 4_379); - assert_eq!(super::compute_total_payout(&I_NPOS, 75_000, 100_000u64, YEAR).0, 2_733); - assert_eq!(super::compute_total_payout(&I_NPOS, 95_000, 100_000u64, YEAR).0, 2_513); - assert_eq!(super::compute_total_payout(&I_NPOS, 100_000, 100_000u64, YEAR).0, 2_505); - - const DAY: u64 = 24 * 60 * 60 * 1000; - assert_eq!(super::compute_total_payout(&I_NPOS, 25_000, 100_000u64, DAY).0, 17); - assert_eq!(super::compute_total_payout(&I_NPOS, 50_000, 100_000u64, DAY).0, 27); - assert_eq!(super::compute_total_payout(&I_NPOS, 75_000, 100_000u64, DAY).0, 7); - - const SIX_HOURS: u64 = 6 * 60 * 60 * 1000; - assert_eq!(super::compute_total_payout(&I_NPOS, 25_000, 100_000u64, SIX_HOURS).0, 4); - assert_eq!(super::compute_total_payout(&I_NPOS, 50_000, 100_000u64, SIX_HOURS).0, 7); - assert_eq!(super::compute_total_payout(&I_NPOS, 75_000, 100_000u64, SIX_HOURS).0, 2); - - const HOUR: u64 = 60 * 60 * 1000; - assert_eq!( - super::compute_total_payout( - &I_NPOS, - 2_500_000_000_000_000_000_000_000_000u128, - 5_000_000_000_000_000_000_000_000_000u128, - HOUR - ) - .0, - 57_038_500_000_000_000_000_000 - ); - } -} diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs deleted file mode 100644 index 57e4edcc5..000000000 --- a/pallets/staking/src/lib.rs +++ /dev/null @@ -1,958 +0,0 @@ -// 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 Pallet -//! -//! The Staking pallet is used to manage funds at stake by network maintainers. -//! -//! - [`Config`] -//! - [`Call`] -//! - [`Pallet`] -//! -//! ## Overview -//! -//! The Staking pallet is the means by which a set of network maintainers (known as _authorities_ in -//! some contexts and _validators_ in others) are chosen based upon those who voluntarily place -//! funds under deposit. Under deposit, those funds are rewarded under normal operation but are held -//! at pain of _slash_ (expropriation) should the staked maintainer be found not to be discharging -//! its duties properly. -//! -//! ### Terminology -//! -//! -//! - Staking: The process of locking up funds for some time, placing them at risk of slashing -//! (loss) in order to become a rewarded maintainer of the network. -//! - Validating: The process of running a node to actively maintain the network, either by -//! producing blocks or guaranteeing finality of the chain. -//! - Nominating: The process of placing staked funds behind one or more validators in order to -//! share in any reward, and punishment, they take. -//! - Stash account: The account holding an owner's funds used for staking. -//! - Controller account: The account that controls an owner's funds for staking. -//! - Era: A (whole) number of sessions, which is the period that the validator set (and each -//! validator's active nominator set) is recalculated and where rewards are paid out. -//! - Slash: The punishment of a staker by reducing its funds. -//! -//! ### Goals -//! -//! -//! The staking system in Substrate NPoS is designed to make the following possible: -//! -//! - Stake funds that are controlled by a cold wallet. -//! - Withdraw some, or deposit more, funds without interrupting the role of an entity. -//! - Switch between roles (nominator, validator, idle) with minimal overhead. -//! -//! ### Scenarios -//! -//! #### Staking -//! -//! Almost any interaction with the Staking pallet requires a process of _**bonding**_ (also known -//! as being a _staker_). To become *bonded*, a fund-holding register known as the _stash account_, -//! which holds some or all of the funds that become frozen in place as part of the staking process, -//! is paired with an active **controller** account, which issues instructions on how they shall be -//! used. -//! -//! An account pair can become bonded using the [`bond`](Call::bond) call. -//! -//! Stash accounts can update their associated controller back to the stash account using the -//! [`set_controller`](Call::set_controller) call. -//! -//! There are three possible roles that any staked account pair can be in: `Validator`, `Nominator` -//! and `Idle` (defined in [`StakerStatus`]). There are three -//! corresponding instructions to change between roles, namely: -//! [`validate`](Call::validate), -//! [`nominate`](Call::nominate), and [`chill`](Call::chill). -//! -//! #### Validating -//! -//! A **validator** takes the role of either validating blocks or ensuring their finality, -//! maintaining the veracity of the network. A validator should avoid both any sort of malicious -//! misbehavior and going offline. Bonded accounts that state interest in being a validator do NOT -//! get immediately chosen as a validator. Instead, they are declared as a _candidate_ and they -//! _might_ get elected at the _next era_ as a validator. The result of the election is determined -//! by nominators and their votes. -//! -//! An account can become a validator candidate via the -//! [`validate`](Call::validate) call. -//! -//! #### Nomination -//! -//! A **nominator** does not take any _direct_ role in maintaining the network, instead, it votes on -//! a set of validators to be elected. Once interest in nomination is stated by an account, it -//! takes effect at the next election round. The funds in the nominator's stash account indicate the -//! _weight_ of its vote. Both the rewards and any punishment that a validator earns are shared -//! between the validator and its nominators. This rule incentivizes the nominators to NOT vote for -//! the misbehaving/offline validators as much as possible, simply because the nominators will also -//! lose funds if they vote poorly. -//! -//! An account can become a nominator via the [`nominate`](Call::nominate) call. -//! -//! #### Voting -//! -//! Staking is closely related to elections; actual validators are chosen from among all potential -//! validators via election by the potential validators and nominators. To reduce use of the phrase -//! "potential validators and nominators", we often use the term **voters**, who are simply -//! the union of potential validators and nominators. -//! -//! #### Rewards and Slash -//! -//! The **reward and slashing** procedure is the core of the Staking pallet, attempting to _embrace -//! valid behavior_ while _punishing any misbehavior or lack of availability_. -//! -//! Rewards must be claimed for each era before it gets too old by `$HISTORY_DEPTH` using the -//! `payout_stakers` call. Any account can call `payout_stakers`, which pays the reward to the -//! validator as well as its nominators. Only the [`Config::MaxNominatorRewardedPerValidator`] -//! biggest stakers can claim their reward. This is to limit the i/o cost to mutate storage for each -//! nominator's account. -//! -//! Slashing can occur at any point in time, once misbehavior is reported. Once slashing is -//! determined, a value is deducted from the balance of the validator and all the nominators who -//! voted for this validator (values are deducted from the _stash_ account of the slashed entity). -//! -//! Slashing logic is further described in the documentation of the `slashing` pallet. -//! -//! Similar to slashing, rewards are also shared among a validator and its associated nominators. -//! Yet, the reward funds are not always transferred to the stash account and can be configured. See -//! [Reward Calculation](#reward-calculation) for more details. -//! -//! #### Chilling -//! -//! Finally, any of the roles above can choose to step back temporarily and just chill for a while. -//! This means that if they are a nominator, they will not be considered as voters anymore and if -//! they are validators, they will no longer be a candidate for the next election. -//! -//! An account can step back via the [`chill`](Call::chill) call. -//! -//! ### Session managing -//! -//! The pallet implement the trait `SessionManager`. Which is the only API to query new validator -//! set and allowing these validator set to be rewarded once their era is ended. -//! -//! ## Interface -//! -//! ### Dispatchable Functions -//! -//! The dispatchable functions of the Staking pallet enable the steps needed for entities to accept -//! and change their role, alongside some helper functions to get/set the metadata of the pallet. -//! -//! ### Public Functions -//! -//! The Staking pallet contains many public storage items and (im)mutable functions. -//! -//! ## Usage -//! -//! ### Example: Rewarding a validator by id. -//! -//! ``` -//! use pallet_staking::{self as staking}; -//! -//! #[frame_support::pallet] -//! pub mod pallet { -//! use super::*; -//! use frame_support::pallet_prelude::*; -//! use frame_system::pallet_prelude::*; -//! -//! #[pallet::pallet] -//! pub struct Pallet(_); -//! -//! #[pallet::config] -//! pub trait Config: frame_system::Config + staking::Config {} -//! -//! #[pallet::call] -//! impl Pallet { -//! /// Reward a validator. -//! #[pallet::weight(0)] -//! pub fn reward_myself(origin: OriginFor) -> DispatchResult { -//! let reported = ensure_signed(origin)?; -//! >::reward_by_ids(vec![(reported, 10)]); -//! Ok(()) -//! } -//! } -//! } -//! # fn main() { } -//! ``` -//! -//! ## Implementation Details -//! -//! ### Era payout -//! -//! The era payout is computed using yearly inflation curve defined at -//! [`Config::EraPayout`] as such: -//! -//! ```nocompile -//! staker_payout = yearly_inflation(npos_token_staked / total_tokens) * total_tokens / era_per_year -//! ``` -//! This payout is used to reward stakers as defined in next section -//! -//! ```nocompile -//! remaining_payout = max_yearly_inflation * total_tokens / era_per_year - staker_payout -//! ``` -//! The remaining reward is send to the configurable end-point -//! [`Config::RewardRemainder`]. -//! -//! ### Reward Calculation -//! -//! Validators and nominators are rewarded at the end of each era. The total reward of an era is -//! calculated using the era duration and the staking rate (the total amount of tokens staked by -//! nominators and validators, divided by the total token supply). It aims to incentivize toward a -//! defined staking rate. The full specification can be found -//! [here](https://research.web3.foundation/en/latest/polkadot/Token%20Economics.html#inflation-model). -//! -//! Total reward is split among validators and their nominators depending on the number of points -//! they received during the era. Points are added to a validator using -//! [`reward_by_ids`](Pallet::reward_by_ids). -//! -//! [`Pallet`] implements -//! [`pallet_authorship::EventHandler`] to add reward -//! points to block producer and block producer of referenced uncles. -//! -//! The validator and its nominator split their reward as following: -//! -//! The validator can declare an amount, named [`commission`](ValidatorPrefs::commission), that does -//! not get shared with the nominators at each reward payout through its [`ValidatorPrefs`]. This -//! value gets deducted from the total reward that is paid to the validator and its nominators. The -//! remaining portion is split pro rata among the validator and the top -//! [`Config::MaxNominatorRewardedPerValidator`] nominators that nominated the validator, -//! proportional to the value staked behind the validator (_i.e._ dividing the -//! [`own`](Exposure::own) or [`others`](Exposure::others) by [`total`](Exposure::total) in -//! [`Exposure`]). Note that the pro rata division of rewards uses the total exposure behind the -//! validator, *not* just the exposure of the validator and the top -//! [`Config::MaxNominatorRewardedPerValidator`] nominators. -//! -//! All entities who receive a reward have the option to choose their reward destination through the -//! [`Payee`] storage item (see -//! [`set_payee`](Call::set_payee)), to be one of the following: -//! -//! - Controller account, (obviously) not increasing the staked value. -//! - Stash account, not increasing the staked value. -//! - Stash account, also increasing the staked value. -//! -//! ### Additional Fund Management Operations -//! -//! Any funds already placed into stash can be the target of the following operations: -//! -//! The controller account can free a portion (or all) of the funds using the -//! [`unbond`](Call::unbond) call. Note that the funds are not immediately -//! accessible. Instead, a duration denoted by -//! [`Config::BondingDuration`] (in number of eras) must -//! pass until the funds can actually be removed. Once the `BondingDuration` is over, the -//! [`withdraw_unbonded`](Call::withdraw_unbonded) call can be used to actually -//! withdraw the funds. -//! -//! Note that there is a limitation to the number of fund-chunks that can be scheduled to be -//! unlocked in the future via [`unbond`](Call::unbond). In case this maximum -//! (`MAX_UNLOCKING_CHUNKS`) is reached, the bonded account _must_ first wait until a successful -//! call to `withdraw_unbonded` to remove some of the chunks. -//! -//! ### Election Algorithm -//! -//! The current election algorithm is implemented based on Phragmén. The reference implementation -//! can be found [here](https://github.com/w3f/consensus/tree/master/NPoS). -//! -//! The election algorithm, aside from electing the validators with the most stake value and votes, -//! tries to divide the nominator votes among candidates in an equal manner. To further assure this, -//! an optional post-processing can be applied that iteratively normalizes the nominator staked -//! values until the total difference among votes of a particular nominator are less than a -//! threshold. -//! -//! ## GenesisConfig -//! -//! The Staking pallet depends on the [`GenesisConfig`]. The -//! `GenesisConfig` is optional and allow to set some initial stakers. -//! -//! ## Related Modules -//! -//! - [Balances](../pallet_balances/index.html): Used to manage values at stake. -//! - [Session](../pallet_session/index.html): Used to manage sessions. Also, a list of new -//! validators is stored in the Session pallet's `Validators` at the end of each era. - -#![cfg_attr(not(feature = "std"), no_std)] -#![recursion_limit = "256"] - -#[cfg(feature = "runtime-benchmarks")] -pub mod benchmarking; -#[cfg(any(feature = "runtime-benchmarks", test))] -pub mod testing_utils; - -#[cfg(test)] -pub(crate) mod mock; -#[cfg(test)] -mod tests; - -pub mod inflation; -pub mod migrations; -pub mod slashing; -pub mod weights; - -mod pallet; - -use codec::{Decode, Encode, HasCompact, MaxEncodedLen}; -use frame_support::{ - traits::{Currency, Defensive, Get}, - weights::Weight, - BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, -}; -pub use pallet::{pallet::*, UseNominatorsAndValidatorsMap, UseValidatorsMap}; -use scale_info::TypeInfo; -use sp_runtime::{ - curve::PiecewiseLinear, - traits::{AtLeast32BitUnsigned, Convert, Saturating, StaticLookup, Zero}, - Perbill, Perquintill, Rounding, RuntimeDebug, -}; -pub use sp_staking::StakerStatus; -use sp_staking::{ - offence::{Offence, OffenceError, ReportOffence}, - EraIndex, OnStakingUpdate, SessionIndex, -}; -use sp_std::{collections::btree_map::BTreeMap, prelude::*}; -pub use weights::WeightInfo; - -pub(crate) const LOG_TARGET: &str = "runtime::staking"; - -// syntactic sugar for logging. -#[macro_export] -macro_rules! log { - ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { - log::$level!( - target: crate::LOG_TARGET, - concat!("[{:?}] 💸 ", $patter), >::block_number() $(, $values)* - ) - }; -} - -/// Maximum number of winners (aka. active validators), as defined in the election provider of this -/// pallet. -pub type MaxWinnersOf = <::ElectionProvider as frame_election_provider_support::ElectionProviderBase>::MaxWinners; - -/// Counter for the number of "reward" points earned by a given validator. -pub type RewardPoint = u32; - -/// The balance type of this pallet. -pub type BalanceOf = ::CurrencyBalance; - -type PositiveImbalanceOf = <::Currency as Currency< - ::AccountId, ->>::PositiveImbalance; -type NegativeImbalanceOf = <::Currency as Currency< - ::AccountId, ->>::NegativeImbalance; - -type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; - -/// Information regarding the active era (era in used in session). -#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct ActiveEraInfo { - /// Index of era. - pub index: EraIndex, - /// Moment of start expressed as millisecond from `$UNIX_EPOCH`. - /// - /// Start can be none if start hasn't been set for the era yet, - /// Start is set on the first on_finalize of the era to guarantee usage of `Time`. - start: Option, -} - -/// Reward points of an era. Used to split era total payout between validators. -/// -/// This points will be used to reward validators and their respective nominators. -#[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct EraRewardPoints { - /// Total number of points. Equals the sum of reward points for each validator. - pub total: RewardPoint, - /// The reward points earned by a given validator. - pub individual: BTreeMap, -} - -impl Default for EraRewardPoints { - fn default() -> Self { - EraRewardPoints { total: Default::default(), individual: BTreeMap::new() } - } -} - -/// A destination account for payment. -#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub enum RewardDestination { - /// Pay into the stash account, increasing the amount at stake accordingly. - Staked, - /// Pay into the stash account, not increasing the amount at stake. - Stash, - /// Pay into the controller account. - Controller, - /// Pay into a specified account. - Account(AccountId), - /// Receive no reward. - None, -} - -impl Default for RewardDestination { - fn default() -> Self { - RewardDestination::Staked - } -} - -/// Preference of what happens regarding validation. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, Default, MaxEncodedLen)] -pub struct ValidatorPrefs { - /// Reward that validator takes up-front; only the rest is split between themselves and - /// nominators. - #[codec(compact)] - pub commission: Perbill, - /// Whether or not this validator is accepting more nominations. If `true`, then no nominator - /// who is not already nominating this validator may nominate them. By default, validators - /// are accepting nominations. - pub blocked: bool, -} - -/// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be unlocked. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct UnlockChunk { - /// Amount of funds to be unlocked. - #[codec(compact)] - value: Balance, - /// Era number at which point it'll be unlocked. - #[codec(compact)] - era: EraIndex, -} - -/// The ledger of a (bonded) stash. -#[derive( - PartialEqNoBound, - EqNoBound, - CloneNoBound, - Encode, - Decode, - RuntimeDebugNoBound, - TypeInfo, - MaxEncodedLen, -)] -#[scale_info(skip_type_params(T))] -pub struct StakingLedger { - /// The stash account whose balance is actually locked and at stake. - pub stash: T::AccountId, - /// The total amount of the stash's balance that we are currently accounting for. - /// It's just `active` plus all the `unlocking` balances. - #[codec(compact)] - pub total: BalanceOf, - /// The total amount of the stash's balance that will be at stake in any forthcoming - /// rounds. - #[codec(compact)] - pub active: BalanceOf, - /// Any balance that is becoming free, which may eventually be transferred out of the stash - /// (assuming it doesn't get slashed first). It is assumed that this will be treated as a first - /// in, first out queue where the new (higher value) eras get pushed on the back. - pub unlocking: BoundedVec>, T::MaxUnlockingChunks>, - /// List of eras for which the stakers behind a validator have claimed rewards. Only updated - /// for validators. - pub claimed_rewards: BoundedVec, -} - -impl StakingLedger { - /// Initializes the default object using the given `validator`. - pub fn default_from(stash: T::AccountId) -> Self { - Self { - stash, - total: Zero::zero(), - active: Zero::zero(), - unlocking: Default::default(), - claimed_rewards: Default::default(), - } - } - - /// Remove entries from `unlocking` that are sufficiently old and reduce the - /// total by the sum of their balances. - fn consolidate_unlocked(self, current_era: EraIndex) -> Self { - let mut total = self.total; - let unlocking: BoundedVec<_, _> = self - .unlocking - .into_iter() - .filter(|chunk| { - if chunk.era > current_era { - true - } else { - total = total.saturating_sub(chunk.value); - false - } - }) - .collect::>() - .try_into() - .expect( - "filtering items from a bounded vec always leaves length less than bounds. qed", - ); - - Self { - stash: self.stash, - total, - active: self.active, - unlocking, - claimed_rewards: self.claimed_rewards, - } - } - - /// Re-bond funds that were scheduled for unlocking. - /// - /// Returns the updated ledger, and the amount actually rebonded. - fn rebond(mut self, value: BalanceOf) -> (Self, BalanceOf) { - let mut unlocking_balance = BalanceOf::::zero(); - - while let Some(last) = self.unlocking.last_mut() { - if unlocking_balance + last.value <= value { - unlocking_balance += last.value; - self.active += last.value; - self.unlocking.pop(); - } else { - let diff = value - unlocking_balance; - - unlocking_balance += diff; - self.active += diff; - last.value -= diff; - } - - if unlocking_balance >= value { - break - } - } - - (self, unlocking_balance) - } - - /// Slash the staker for a given amount of balance. - /// - /// This implements a proportional slashing system, whereby we set our preference to slash as - /// such: - /// - /// - If any unlocking chunks exist that are scheduled to be unlocked at `slash_era + - /// bonding_duration` and onwards, the slash is divided equally between the active ledger and - /// the unlocking chunks. - /// - If no such chunks exist, then only the active balance is slashed. - /// - /// Note that the above is only a *preference*. If for any reason the active ledger, with or - /// without some portion of the unlocking chunks that are more justified to be slashed are not - /// enough, then the slashing will continue and will consume as much of the active and unlocking - /// chunks as needed. - /// - /// This will never slash more than the given amount. If any of the chunks become dusted, the - /// last chunk is slashed slightly less to compensate. Returns the amount of funds actually - /// slashed. - /// - /// `slash_era` is the era in which the slash (which is being enacted now) actually happened. - /// - /// This calls `Config::OnStakingUpdate::on_slash` with information as to how the slash was - /// applied. - pub fn slash( - &mut self, - slash_amount: BalanceOf, - minimum_balance: BalanceOf, - slash_era: EraIndex, - ) -> BalanceOf { - if slash_amount.is_zero() { - return Zero::zero() - } - - use sp_runtime::PerThing as _; - let mut remaining_slash = slash_amount; - let pre_slash_total = self.total; - - // for a `slash_era = x`, any chunk that is scheduled to be unlocked at era `x + 28` - // (assuming 28 is the bonding duration) onwards should be slashed. - let slashable_chunks_start = slash_era + T::BondingDuration::get(); - - // `Some(ratio)` if this is proportional, with `ratio`, `None` otherwise. In both cases, we - // slash first the active chunk, and then `slash_chunks_priority`. - let (maybe_proportional, slash_chunks_priority) = { - if let Some(first_slashable_index) = - self.unlocking.iter().position(|c| c.era >= slashable_chunks_start) - { - // If there exists a chunk who's after the first_slashable_start, then this is a - // proportional slash, because we want to slash active and these chunks - // proportionally. - - // The indices of the first chunk after the slash up through the most recent chunk. - // (The most recent chunk is at greatest from this era) - let affected_indices = first_slashable_index..self.unlocking.len(); - let unbonding_affected_balance = - affected_indices.clone().fold(BalanceOf::::zero(), |sum, i| { - if let Some(chunk) = self.unlocking.get(i).defensive() { - sum.saturating_add(chunk.value) - } else { - sum - } - }); - let affected_balance = self.active.saturating_add(unbonding_affected_balance); - let ratio = Perquintill::from_rational_with_rounding( - slash_amount, - affected_balance, - Rounding::Up, - ) - .unwrap_or_else(|_| Perquintill::one()); - ( - Some(ratio), - affected_indices.chain((0..first_slashable_index).rev()).collect::>(), - ) - } else { - // We just slash from the last chunk to the most recent one, if need be. - (None, (0..self.unlocking.len()).rev().collect::>()) - } - }; - - // Helper to update `target` and the ledgers total after accounting for slashing `target`. - log!( - debug, - "slashing {:?} for era {:?} out of {:?}, priority: {:?}, proportional = {:?}", - slash_amount, - slash_era, - self, - slash_chunks_priority, - maybe_proportional, - ); - - let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { - let mut slash_from_target = if let Some(ratio) = maybe_proportional { - ratio.mul_ceil(*target) - } else { - *slash_remaining - } - // this is the total that that the slash target has. We can't slash more than - // this anyhow! - .min(*target) - // this is the total amount that we would have wanted to slash - // non-proportionally, a proportional slash should never exceed this either! - .min(*slash_remaining); - - // slash out from *target exactly `slash_from_target`. - *target = *target - slash_from_target; - if *target < minimum_balance { - // Slash the rest of the target if it's dust. This might cause the last chunk to be - // slightly under-slashed, by at most `MaxUnlockingChunks * ED`, which is not a big - // deal. - slash_from_target = - sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) - } - - self.total = self.total.saturating_sub(slash_from_target); - *slash_remaining = slash_remaining.saturating_sub(slash_from_target); - }; - - // If this is *not* a proportional slash, the active will always wiped to 0. - slash_out_of(&mut self.active, &mut remaining_slash); - - let mut slashed_unlocking = BTreeMap::<_, _>::new(); - for i in slash_chunks_priority { - if remaining_slash.is_zero() { - break - } - - if let Some(chunk) = self.unlocking.get_mut(i).defensive() { - slash_out_of(&mut chunk.value, &mut remaining_slash); - // write the new slashed value of this chunk to the map. - slashed_unlocking.insert(chunk.era, chunk.value); - } else { - break - } - } - - // clean unlocking chunks that are set to zero. - self.unlocking.retain(|c| !c.value.is_zero()); - - T::EventListeners::on_slash(&self.stash, self.active, &slashed_unlocking); - pre_slash_total.saturating_sub(self.total) - } -} - -/// A record of the nominations made by a specific account. -#[derive( - PartialEqNoBound, EqNoBound, Clone, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen, -)] -#[codec(mel_bound())] -#[scale_info(skip_type_params(T))] -pub struct Nominations { - /// The targets of nomination. - pub targets: BoundedVec, - /// The era the nominations were submitted. - /// - /// Except for initial nominations which are considered submitted at era 0. - pub submitted_in: EraIndex, - /// Whether the nominations have been suppressed. This can happen due to slashing of the - /// validators, or other events that might invalidate the nomination. - /// - /// NOTE: this for future proofing and is thus far not used. - pub suppressed: bool, -} - -/// The amount of exposure (to slashing) than an individual nominator has. -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct IndividualExposure { - /// The stash account of the nominator in question. - pub who: AccountId, - /// Amount of funds exposed. - #[codec(compact)] - pub value: Balance, -} - -/// A snapshot of the stake backing a single validator in the system. -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct Exposure { - /// The total balance backing this validator. - #[codec(compact)] - pub total: Balance, - /// The validator's own stash that is exposed. - #[codec(compact)] - pub own: Balance, - /// The portions of nominators stashes that are exposed. - pub others: Vec>, -} - -impl Default for Exposure { - fn default() -> Self { - Self { total: Default::default(), own: Default::default(), others: vec![] } - } -} - -/// 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, TypeInfo)] -pub struct UnappliedSlash { - /// The stash ID of the offending validator. - validator: AccountId, - /// The validator's own slash. - own: Balance, - /// All other slashed stakers and amounts. - others: Vec<(AccountId, Balance)>, - /// Reporters of the offence; bounty payout recipients. - reporters: Vec, - /// The amount of payout. - payout: Balance, -} - -impl UnappliedSlash { - /// Initializes the default object using the given `validator`. - pub fn default_from(validator: AccountId) -> Self { - Self { - validator, - own: Zero::zero(), - others: vec![], - reporters: vec![], - payout: Zero::zero(), - } - } -} - -/// Means for interacting with a specialized version of the `session` trait. -/// -/// This is needed because `Staking` sets the `ValidatorIdOf` of the `pallet_session::Config` -pub trait SessionInterface { - /// Disable the validator at the given index, returns `false` if the validator was already - /// disabled or the index is out of bounds. - fn disable_validator(validator_index: u32) -> bool; - /// Get the validators from session. - fn validators() -> Vec; - /// Prune historical session tries up to but not including the given index. - fn prune_historical_up_to(up_to: SessionIndex); -} - -impl SessionInterface<::AccountId> for T -where - T: pallet_session::Config::AccountId>, - T: pallet_session::historical::Config< - FullIdentification = Exposure<::AccountId, BalanceOf>, - FullIdentificationOf = ExposureOf, - >, - T::SessionHandler: pallet_session::SessionHandler<::AccountId>, - T::SessionManager: pallet_session::SessionManager<::AccountId>, - T::ValidatorIdOf: Convert< - ::AccountId, - Option<::AccountId>, - >, -{ - fn disable_validator(validator_index: u32) -> bool { - >::disable_index(validator_index) - } - - fn validators() -> Vec<::AccountId> { - >::validators() - } - - fn prune_historical_up_to(up_to: SessionIndex) { - >::prune_up_to(up_to); - } -} - -impl SessionInterface for () { - fn disable_validator(_: u32) -> bool { - true - } - fn validators() -> Vec { - Vec::new() - } - fn prune_historical_up_to(_: SessionIndex) { - () - } -} - -/// Handler for determining how much of a balance should be paid out on the current era. -pub trait EraPayout { - /// Determine the payout for this era. - /// - /// Returns the amount to be paid to stakers in this era, as well as whatever else should be - /// paid out ("the rest"). - fn era_payout( - total_staked: Balance, - total_issuance: Balance, - era_duration_millis: u64, - ) -> (Balance, Balance); -} - -impl EraPayout for () { - fn era_payout( - _total_staked: Balance, - _total_issuance: Balance, - _era_duration_millis: u64, - ) -> (Balance, Balance) { - (Default::default(), Default::default()) - } -} - -/// Adaptor to turn a `PiecewiseLinear` curve definition into an `EraPayout` impl, used for -/// backwards compatibility. -pub struct ConvertCurve(sp_std::marker::PhantomData); -impl>> - EraPayout for ConvertCurve -{ - fn era_payout( - total_staked: Balance, - total_issuance: Balance, - era_duration_millis: u64, - ) -> (Balance, Balance) { - let (validator_payout, max_payout) = inflation::compute_total_payout( - T::get(), - total_staked, - total_issuance, - // Duration of era; more than u64::MAX is rewarded as u64::MAX. - era_duration_millis, - ); - let rest = max_payout.saturating_sub(validator_payout.clone()); - (validator_payout, rest) - } -} - -/// Mode of era-forcing. -#[derive( - Copy, - Clone, - PartialEq, - Eq, - Encode, - Decode, - RuntimeDebug, - TypeInfo, - MaxEncodedLen, - serde::Serialize, - serde::Deserialize, -)] -pub enum Forcing { - /// Not forcing anything - just let whatever happen. - NotForcing, - /// Force a new era, then reset to `NotForcing` as soon as it is done. - /// Note that this will force to trigger an election until a new era is triggered, if the - /// election failed, the next session end will trigger a new election again, until success. - ForceNew, - /// Avoid a new era indefinitely. - ForceNone, - /// Force a new era at the end of all sessions indefinitely. - ForceAlways, -} - -impl Default for Forcing { - fn default() -> Self { - Forcing::NotForcing - } -} - -/// A `Convert` implementation that finds the stash of the given controller account, -/// if any. -pub struct StashOf(sp_std::marker::PhantomData); - -impl Convert> for StashOf { - fn convert(controller: T::AccountId) -> Option { - >::ledger(&controller).map(|l| l.stash) - } -} - -/// A typed conversion from stash account ID to the active exposure of nominators -/// on that account. -/// -/// Active exposure is the exposure of the validator set currently validating, i.e. in -/// `active_era`. It can differ from the latest planned exposure in `current_era`. -pub struct ExposureOf(sp_std::marker::PhantomData); - -impl Convert>>> - for ExposureOf -{ - fn convert(validator: T::AccountId) -> Option>> { - >::active_era() - .map(|active_era| >::eras_stakers(active_era.index, &validator)) - } -} - -/// Filter historical offences out and only allow those from the bonding period. -pub struct FilterHistoricalOffences { - _inner: sp_std::marker::PhantomData<(T, R)>, -} - -impl ReportOffence - for FilterHistoricalOffences, R> -where - T: Config, - R: ReportOffence, - O: Offence, -{ - fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError> { - // Disallow any slashing from before the current bonding period. - let offence_session = offence.session_index(); - let bonded_eras = BondedEras::::get(); - - if bonded_eras.first().filter(|(_, start)| offence_session >= *start).is_some() { - R::report_offence(reporters, offence) - } else { - >::deposit_event(Event::::OldSlashingReportDiscarded { - session_index: offence_session, - }); - Ok(()) - } - } - - fn is_known_offence(offenders: &[Offender], time_slot: &O::TimeSlot) -> bool { - R::is_known_offence(offenders, time_slot) - } -} - -/// Configurations of the benchmarking of the pallet. -pub trait BenchmarkingConfig { - /// The maximum number of validators to use. - type MaxValidators: Get; - /// The maximum number of nominators to use. - type MaxNominators: Get; -} - -/// A mock benchmarking config for pallet-staking. -/// -/// Should only be used for testing. -#[cfg(feature = "std")] -pub struct TestBenchmarkingConfig; - -#[cfg(feature = "std")] -impl BenchmarkingConfig for TestBenchmarkingConfig { - type MaxValidators = frame_support::traits::ConstU32<100>; - type MaxNominators = frame_support::traits::ConstU32<100>; -} diff --git a/pallets/staking/src/migrations.rs b/pallets/staking/src/migrations.rs deleted file mode 100644 index 83a9be204..000000000 --- a/pallets/staking/src/migrations.rs +++ /dev/null @@ -1,503 +0,0 @@ -// 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 - -//! Storage migrations for the Staking pallet. - -use frame_election_provider_support::SortedListProvider; -#[cfg(feature = "try-runtime")] -use frame_support::ensure; -use frame_support::{ - dispatch::GetStorageVersion, pallet_prelude::ValueQuery, storage_alias, - traits::OnRuntimeUpgrade, -}; -#[cfg(feature = "try-runtime")] -use sp_runtime::TryRuntimeError; - -use super::*; - -/// Used for release versioning upto v12. -/// -/// Obsolete from v13. Keeping around to make encoding/decoding of old migration code easier. -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -enum ObsoleteReleases { - V1_0_0Ancient, - V2_0_0, - V3_0_0, - V4_0_0, - V5_0_0, // blockable validators. - V6_0_0, // removal of all storage associated with offchain phragmen. - V7_0_0, // keep track of number of nominators / validators in map - V8_0_0, // populate `VoterList`. - V9_0_0, // inject validators into `VoterList` as well. - V10_0_0, // remove `EarliestUnappliedSlash`. - V11_0_0, // Move pallet storage prefix, e.g. BagsList -> VoterBagsList - V12_0_0, // remove `HistoryDepth`. -} - -impl Default for ObsoleteReleases { - fn default() -> Self { - ObsoleteReleases::V12_0_0 - } -} - -/// Alias to the old storage item used for release versioning. Obsolete since v13. -#[storage_alias] -type StorageVersion = StorageValue, ObsoleteReleases, ValueQuery>; - -pub mod v13 { - use super::*; - - pub struct MigrateToV13(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for MigrateToV13 { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, TryRuntimeError> { - frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V12_0_0, - "Required v12 before upgrading to v13" - ); - - Ok(Default::default()) - } - - fn on_runtime_upgrade() -> Weight { - let current = Pallet::::current_storage_version(); - let onchain = StorageVersion::::get(); - - if current == 13 && onchain == ObsoleteReleases::V12_0_0 { - StorageVersion::::kill(); - current.put::>(); - - log!(info, "v13 applied successfully"); - T::DbWeight::get().reads_writes(1, 2) - } else { - log!(warn, "Skipping v13, should be removed"); - T::DbWeight::get().reads(1) - } - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { - frame_support::ensure!( - Pallet::::on_chain_storage_version() == 13, - "v13 not applied" - ); - - frame_support::ensure!( - !StorageVersion::::exists(), - "Storage version not migrated correctly" - ); - - Ok(()) - } - } -} - -pub mod v12 { - use frame_support::{pallet_prelude::ValueQuery, storage_alias}; - - use super::*; - - #[storage_alias] - type HistoryDepth = StorageValue, u32, ValueQuery>; - - /// Clean up `HistoryDepth` from storage. - /// - /// We will be depending on the configurable value of `HistoryDepth` post - /// this release. - pub struct MigrateToV12(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for MigrateToV12 { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, TryRuntimeError> { - frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V11_0_0, - "Expected v11 before upgrading to v12" - ); - - if HistoryDepth::::exists() { - frame_support::ensure!( - T::HistoryDepth::get() == HistoryDepth::::get(), - "Provided value of HistoryDepth should be same as the existing storage value" - ); - } else { - log::info!("No HistoryDepth in storage; nothing to remove"); - } - - Ok(Default::default()) - } - - fn on_runtime_upgrade() -> frame_support::weights::Weight { - if StorageVersion::::get() == ObsoleteReleases::V11_0_0 { - HistoryDepth::::kill(); - StorageVersion::::put(ObsoleteReleases::V12_0_0); - - log!(info, "v12 applied successfully"); - T::DbWeight::get().reads_writes(1, 2) - } else { - log!(warn, "Skipping v12, should be removed"); - T::DbWeight::get().reads(1) - } - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { - frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V12_0_0, - "v12 not applied" - ); - Ok(()) - } - } -} - -pub mod v11 { - use frame_support::{ - storage::migration::move_pallet, - traits::{GetStorageVersion, PalletInfoAccess}, - }; - #[cfg(feature = "try-runtime")] - use sp_io::hashing::twox_128; - - use super::*; - - pub struct MigrateToV11(sp_std::marker::PhantomData<(T, P, N)>); - impl> OnRuntimeUpgrade - for MigrateToV11 - { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, TryRuntimeError> { - frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V10_0_0, - "must upgrade linearly" - ); - let old_pallet_prefix = twox_128(N::get().as_bytes()); - - frame_support::ensure!( - sp_io::storage::next_key(&old_pallet_prefix).is_some(), - "no data for the old pallet name has been detected" - ); - - Ok(Default::default()) - } - - /// Migrate the entire storage of this pallet to a new prefix. - /// - /// Since Cere is already using VoterList, we just need to remove the old prefix - /// - /// This new prefix must be the same as the one set in construct_runtime. For safety, use - /// `PalletInfo` to get it, as: - /// `::PalletInfo::name::`. - /// - /// The migration will look into the storage version in order to avoid triggering a - /// migration on an up to date storage. - fn on_runtime_upgrade() -> Weight { - if StorageVersion::::get() == ObsoleteReleases::V10_0_0 { - log!(info, "removing {}", N::get()); - - let _ = frame_support::storage::migration::clear_storage_prefix( - N::get().as_bytes(), - &[], - &[], - None, - None, - ); - - StorageVersion::::put(ObsoleteReleases::V11_0_0); - - ::BlockWeights::get().max_block - } else { - log!(warn, "v11::migrate should be removed."); - T::DbWeight::get().reads(1) - } - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { - frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V11_0_0, - "wrong version after the upgrade" - ); - - let old_pallet_name = N::get(); - let new_pallet_name =

::name(); - - // skip storage prefix checks for the same pallet names - if new_pallet_name == old_pallet_name { - return Ok(()) - } - - let old_pallet_prefix = twox_128(N::get().as_bytes()); - frame_support::ensure!( - sp_io::storage::next_key(&old_pallet_prefix).is_none(), - "old pallet data hasn't been removed" - ); - - let new_pallet_name =

::name(); - let new_pallet_prefix = twox_128(new_pallet_name.as_bytes()); - frame_support::ensure!( - sp_io::storage::next_key(&new_pallet_prefix).is_some(), - "new pallet data hasn't been created" - ); - - Ok(()) - } - } -} - -pub mod v10 { - use frame_support::storage_alias; - - use super::*; - - #[storage_alias] - type EarliestUnappliedSlash = StorageValue, EraIndex>; - - /// Apply any pending slashes that where queued. - /// - /// That means we might slash someone a bit too early, but we will definitely - /// won't forget to slash them. The cap of 512 is somewhat randomly taken to - /// prevent us from iterating over an arbitrary large number of keys `on_runtime_upgrade`. - pub struct MigrateToV10(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for MigrateToV10 { - fn on_runtime_upgrade() -> frame_support::weights::Weight { - if StorageVersion::::get() == ObsoleteReleases::V9_0_0 { - let pending_slashes = UnappliedSlashes::::iter().take(512); - for (era, slashes) in pending_slashes { - for slash in slashes { - // in the old slashing scheme, the slash era was the key at which we read - // from `UnappliedSlashes`. - log!(warn, "prematurely applying a slash ({:?}) for era {:?}", slash, era); - slashing::apply_slash::(slash, era); - } - } - - EarliestUnappliedSlash::::kill(); - StorageVersion::::put(ObsoleteReleases::V10_0_0); - - log!(info, "MigrateToV10 executed successfully"); - T::DbWeight::get().reads_writes(1, 1) - } else { - log!(warn, "MigrateToV10 should be removed."); - T::DbWeight::get().reads(1) - } - } - } -} - -pub mod v9 { - #[cfg(feature = "try-runtime")] - use frame_support::codec::{Decode, Encode}; - #[cfg(feature = "try-runtime")] - use sp_std::vec::Vec; - - use super::*; - - /// Migration implementation that injects all validators into sorted list. - /// - /// Skip the migration and update only StorageVersion since Cere already has all - /// validators in the list - /// - /// This is only useful for chains that started their `VoterList` just based on nominators. - pub struct InjectValidatorsIntoVoterList(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { - fn on_runtime_upgrade() -> Weight { - if StorageVersion::::get() == ObsoleteReleases::V8_0_0 { - log!(info, "migrating staking to ObsoleteReleases::V9_0_0"); - - StorageVersion::::put(ObsoleteReleases::V9_0_0); - T::DbWeight::get().reads_writes(1, 1) - } else { - log!( - warn, - "InjectValidatorsIntoVoterList being executed on the wrong storage \ - version, expected ObsoleteReleases::V8_0_0" - ); - T::DbWeight::get().reads(1) - } - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, TryRuntimeError> { - frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V8_0_0, - "must upgrade linearly" - ); - - let prev_count = T::VoterList::count(); - Ok(prev_count.encode()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(prev_count: Vec) -> Result<(), TryRuntimeError> { - let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect( - "the state parameter should be something that was generated by pre_upgrade", - ); - let post_count = T::VoterList::count(); - ensure!( - post_count == prev_count, - "`VoterList` count after the migration must equal to the sum of \ - previous count since we just skipping the migration" - ); - - frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V9_0_0, - "must upgrade" - ); - Ok(()) - } - } -} - -pub mod v8 { - use frame_election_provider_support::SortedListProvider; - use frame_support::traits::Get; - - use super::*; - use crate::{Config, Nominators, Pallet, Weight}; - - #[cfg(feature = "try-runtime")] - pub fn pre_migrate() -> Result<(), &'static str> { - frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V7_0_0, - "must upgrade linearly" - ); - - crate::log!(info, "👜 staking bags-list migration passes PRE migrate checks ✅",); - Ok(()) - } - - /// Migration to sorted `VoterList`. - pub fn migrate() -> Weight { - if StorageVersion::::get() == ObsoleteReleases::V7_0_0 { - crate::log!(info, "migrating staking to ObsoleteReleases::V8_0_0"); - - let migrated = T::VoterList::unsafe_regenerate( - Nominators::::iter().map(|(id, _)| id), - Pallet::::weight_of_fn(), - ); - - StorageVersion::::put(ObsoleteReleases::V8_0_0); - crate::log!( - info, - "👜 completed staking migration to ObsoleteReleases::V8_0_0 with {} voters migrated", - migrated, - ); - - T::BlockWeights::get().max_block - } else { - T::DbWeight::get().reads(1) - } - } - - #[cfg(feature = "try-runtime")] - pub fn post_migrate() -> Result<(), &'static str> { - T::VoterList::try_state().map_err(|_| "VoterList is not in a sane state.")?; - crate::log!(info, "👜 staking bags-list migration passes POST migrate checks ✅",); - Ok(()) - } -} - -pub mod v7 { - use frame_support::storage_alias; - - use super::*; - - #[storage_alias] - type CounterForValidators = StorageValue, u32>; - #[storage_alias] - type CounterForNominators = StorageValue, u32>; - - pub fn pre_migrate() -> Result<(), &'static str> { - assert!( - CounterForValidators::::get().unwrap().is_zero(), - "CounterForValidators already set." - ); - assert!( - CounterForNominators::::get().unwrap().is_zero(), - "CounterForNominators already set." - ); - assert!(Validators::::count().is_zero(), "Validators already set."); - assert!(Nominators::::count().is_zero(), "Nominators already set."); - assert!(StorageVersion::::get() == ObsoleteReleases::V6_0_0); - Ok(()) - } - - pub fn migrate() -> Weight { - log!(info, "Migrating staking to ObsoleteReleases::V7_0_0"); - let validator_count = Validators::::iter().count() as u32; - let nominator_count = Nominators::::iter().count() as u32; - - CounterForValidators::::put(validator_count); - CounterForNominators::::put(nominator_count); - - StorageVersion::::put(ObsoleteReleases::V7_0_0); - log!(info, "Completed staking migration to ObsoleteReleases::V7_0_0"); - - T::DbWeight::get().reads_writes(validator_count.saturating_add(nominator_count).into(), 2) - } -} - -pub mod v6 { - use frame_support::{storage_alias, traits::Get, weights::Weight}; - - use super::*; - - // NOTE: value type doesn't matter, we just set it to () here. - #[storage_alias] - type SnapshotValidators = StorageValue, ()>; - #[storage_alias] - type SnapshotNominators = StorageValue, ()>; - #[storage_alias] - type QueuedElected = StorageValue, ()>; - #[storage_alias] - type QueuedScore = StorageValue, ()>; - #[storage_alias] - type EraElectionStatus = StorageValue, ()>; - #[storage_alias] - type IsCurrentSessionFinal = StorageValue, ()>; - - /// check to execute prior to migration. - pub fn pre_migrate() -> Result<(), &'static str> { - // these may or may not exist. - log!(info, "SnapshotValidators.exits()? {:?}", SnapshotValidators::::exists()); - log!(info, "SnapshotNominators.exits()? {:?}", SnapshotNominators::::exists()); - log!(info, "QueuedElected.exits()? {:?}", QueuedElected::::exists()); - log!(info, "QueuedScore.exits()? {:?}", QueuedScore::::exists()); - // these must exist. - assert!( - IsCurrentSessionFinal::::exists(), - "IsCurrentSessionFinal storage item not found!" - ); - assert!(EraElectionStatus::::exists(), "EraElectionStatus storage item not found!"); - Ok(()) - } - - /// Migrate storage to v6. - pub fn migrate() -> Weight { - log!(info, "Migrating staking to ObsoleteReleases::V6_0_0"); - - SnapshotValidators::::kill(); - SnapshotNominators::::kill(); - QueuedElected::::kill(); - QueuedScore::::kill(); - EraElectionStatus::::kill(); - IsCurrentSessionFinal::::kill(); - - StorageVersion::::put(ObsoleteReleases::V6_0_0); - - log!(info, "Done."); - T::DbWeight::get().writes(6 + 1) - } -} diff --git a/pallets/staking/src/mock.rs b/pallets/staking/src/mock.rs deleted file mode 100644 index d9023c9a2..000000000 --- a/pallets/staking/src/mock.rs +++ /dev/null @@ -1,808 +0,0 @@ -// 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. - -//! Test utilities - -use frame_election_provider_support::{onchain, SequentialPhragmen, VoteWeight}; -use frame_support::{ - assert_ok, ord_parameter_types, parameter_types, - traits::{ - ConstU32, ConstU64, Currency, EitherOfDiverse, FindAuthor, Get, Hooks, Imbalance, - OnUnbalanced, OneSessionHandler, - }, - weights::constants::RocksDbWeight, -}; -use frame_system::{EnsureRoot, EnsureSignedBy}; -use sp_core::H256; -use sp_io; -use sp_runtime::{ - curve::PiecewiseLinear, - testing::UintAuthorityId, - traits::{IdentityLookup, Zero}, - BuildStorage, -}; -use sp_staking::offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}; - -use crate::{self as pallet_staking, *}; - -pub const INIT_TIMESTAMP: u64 = 30_000; -pub const BLOCK_TIME: u64 = 1000; - -/// The AccountId alias in this test module. -pub(crate) type AccountId = u64; -pub(crate) type Nonce = u64; -pub(crate) type BlockNumber = u64; -pub(crate) type Balance = u128; - -/// Another session handler struct to test on_disabled. -pub struct OtherSessionHandler; -impl OneSessionHandler for OtherSessionHandler { - type Key = UintAuthorityId; - - fn on_genesis_session<'a, I: 'a>(_: I) - where - I: Iterator, - AccountId: 'a, - { - } - - fn on_new_session<'a, I: 'a>(_: bool, _: I, _: I) - where - I: Iterator, - AccountId: 'a, - { - } - - fn on_disabled(_validator_index: u32) {} -} - -impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { - type Public = UintAuthorityId; -} - -pub fn is_disabled(controller: AccountId) -> bool { - let stash = Staking::ledger(&controller).unwrap().stash; - let validator_index = match Session::validators().iter().position(|v| *v == stash) { - Some(index) => index as u32, - None => return false, - }; - - Session::disabled_validators().contains(&validator_index) -} - -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub enum Test - { - System: frame_system, - Authorship: pallet_authorship, - Timestamp: pallet_timestamp, - Balances: pallet_balances, - Staking: pallet_staking, - Session: pallet_session, - Historical: pallet_session::historical, - VoterBagsList: pallet_bags_list::, - } -); - -/// Author of block is always 11 -pub struct Author11; -impl FindAuthor for Author11 { - fn find_author<'a, I>(_digests: I) -> Option - where - I: 'a + IntoIterator, - { - Some(11) - } -} - -parameter_types! { - pub static SessionsPerEra: SessionIndex = 3; - pub static ExistentialDeposit: Balance = 1; - pub static SlashDeferDuration: EraIndex = 0; - pub static Period: BlockNumber = 5; - pub static Offset: BlockNumber = 0; -} - -impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = RocksDbWeight; - type RuntimeOrigin = RuntimeOrigin; - type Nonce = Nonce; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = frame_support::traits::ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; -} -impl pallet_balances::Config for Test { - type MaxLocks = frame_support::traits::ConstU32<1024>; - type MaxReserves = (); - type ReserveIdentifier = [u8; 8]; - type Balance = Balance; - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = (); - type FreezeIdentifier = (); - type MaxFreezes = (); - type RuntimeHoldReason = (); - type MaxHolds = (); -} - -sp_runtime::impl_opaque_keys! { - pub struct SessionKeys { - pub other: OtherSessionHandler, - } -} -impl pallet_session::Config for Test { - type SessionManager = pallet_session::historical::NoteHistoricalRoot; - type Keys = SessionKeys; - type ShouldEndSession = pallet_session::PeriodicSessions; - type SessionHandler = (OtherSessionHandler,); - type RuntimeEvent = RuntimeEvent; - type ValidatorId = AccountId; - type ValidatorIdOf = crate::StashOf; - type NextSessionRotation = pallet_session::PeriodicSessions; - type WeightInfo = (); -} - -impl pallet_session::historical::Config for Test { - type FullIdentification = crate::Exposure; - type FullIdentificationOf = crate::ExposureOf; -} -impl pallet_authorship::Config for Test { - type FindAuthor = Author11; - type EventHandler = Pallet; -} - -impl pallet_timestamp::Config for Test { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = ConstU64<5>; - type WeightInfo = (); -} - -pallet_staking_reward_curve::build! { - const I_NPOS: PiecewiseLinear<'static> = curve!( - min_inflation: 0_025_000, - max_inflation: 0_100_000, - ideal_stake: 0_500_000, - falloff: 0_050_000, - max_piece_count: 40, - test_precision: 0_005_000, - ); -} -parameter_types! { - pub const BondingDuration: EraIndex = 3; - pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS; - pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(75); -} - -parameter_types! { - pub static RewardRemainderUnbalanced: u128 = 0; -} - -pub struct RewardRemainderMock; - -impl OnUnbalanced> for RewardRemainderMock { - fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { - RewardRemainderUnbalanced::mutate(|v| { - *v += amount.peek(); - }); - drop(amount); - } -} - -const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = - [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; - -parameter_types! { - pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; - pub static MaxNominations: u32 = 16; - pub static HistoryDepth: u32 = 80; - pub static MaxUnlockingChunks: u32 = 32; - pub static RewardOnUnbalanceWasCalled: bool = false; - pub static MaxWinners: u32 = 100; -} - -type VoterBagsListInstance = pallet_bags_list::Instance1; -impl pallet_bags_list::Config for Test { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); - // Staking is the source of truth for voter bags list, since they are not kept up to date. - type ScoreProvider = Staking; - type BagThresholds = BagThresholds; - type Score = VoteWeight; -} - -pub struct OnChainSeqPhragmen; -impl onchain::Config for OnChainSeqPhragmen { - type System = Test; - type Solver = SequentialPhragmen; - type DataProvider = Staking; - type WeightInfo = (); - type MaxWinners = MaxWinners; - type VotersBound = ConstU32<{ u32::MAX }>; - type TargetsBound = ConstU32<{ u32::MAX }>; -} - -pub struct MockReward {} -impl OnUnbalanced> for MockReward { - fn on_unbalanced(_: PositiveImbalanceOf) { - RewardOnUnbalanceWasCalled::set(true); - } -} - -parameter_types! { - pub static LedgerSlashPerEra: - (BalanceOf, BTreeMap>) = - (Zero::zero(), BTreeMap::new()); -} - -pub struct EventListenerMock; -impl OnStakingUpdate for EventListenerMock { - fn on_slash( - _pool_account: &AccountId, - slashed_bonded: Balance, - slashed_chunks: &BTreeMap, - ) { - LedgerSlashPerEra::set((slashed_bonded, slashed_chunks.clone())); - } -} - -impl crate::pallet::pallet::Config for Test { - type MaxNominations = MaxNominations; - type Currency = Balances; - type CurrencyBalance = ::Balance; - type UnixTime = Timestamp; - type CurrencyToVote = (); - type RewardRemainder = RewardRemainderMock; - type RuntimeEvent = RuntimeEvent; - type Slash = (); - type Reward = MockReward; - type SessionsPerEra = SessionsPerEra; - type SlashDeferDuration = SlashDeferDuration; - type AdminOrigin = EnsureOneOrRoot; - type BondingDuration = BondingDuration; - type SessionInterface = Self; - type EraPayout = ConvertCurve; - type NextNewSession = Session; - type MaxNominatorRewardedPerValidator = ConstU32<64>; - type OffendingValidatorsThreshold = OffendingValidatorsThreshold; - type ElectionProvider = onchain::OnChainExecution; - type GenesisElectionProvider = Self::ElectionProvider; - // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. - type VoterList = VoterBagsList; - type TargetList = UseValidatorsMap; - type MaxUnlockingChunks = MaxUnlockingChunks; - type HistoryDepth = HistoryDepth; - type EventListeners = EventListenerMock; - type BenchmarkingConfig = TestBenchmarkingConfig; - type WeightInfo = (); -} - -pub(crate) type StakingCall = crate::Call; -pub(crate) type TestCall = ::RuntimeCall; - -pub struct ExtBuilder { - nominate: bool, - validator_count: u32, - minimum_validator_count: u32, - invulnerables: Vec, - has_stakers: bool, - initialize_first_session: bool, - pub min_nominator_bond: Balance, - min_validator_bond: Balance, - balance_factor: Balance, - status: BTreeMap>, - stakes: BTreeMap, - stakers: Vec<(AccountId, AccountId, Balance, StakerStatus)>, -} - -impl Default for ExtBuilder { - fn default() -> Self { - Self { - nominate: true, - validator_count: 2, - minimum_validator_count: 0, - balance_factor: 1, - invulnerables: vec![], - has_stakers: true, - initialize_first_session: true, - min_nominator_bond: ExistentialDeposit::get(), - min_validator_bond: ExistentialDeposit::get(), - status: Default::default(), - stakes: Default::default(), - stakers: Default::default(), - } - } -} - -impl ExtBuilder { - pub fn existential_deposit(self, existential_deposit: Balance) -> Self { - EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = existential_deposit); - self - } - pub fn nominate(mut self, nominate: bool) -> Self { - self.nominate = nominate; - self - } - pub fn validator_count(mut self, count: u32) -> Self { - self.validator_count = count; - self - } - pub fn minimum_validator_count(mut self, count: u32) -> Self { - self.minimum_validator_count = count; - self - } - pub fn slash_defer_duration(self, eras: EraIndex) -> Self { - SLASH_DEFER_DURATION.with(|v| *v.borrow_mut() = eras); - self - } - pub fn invulnerables(mut self, invulnerables: Vec) -> Self { - self.invulnerables = invulnerables; - self - } - pub fn session_per_era(self, length: SessionIndex) -> Self { - SESSIONS_PER_ERA.with(|v| *v.borrow_mut() = length); - self - } - pub fn period(self, length: BlockNumber) -> Self { - PERIOD.with(|v| *v.borrow_mut() = length); - self - } - pub fn has_stakers(mut self, has: bool) -> Self { - self.has_stakers = has; - self - } - pub fn initialize_first_session(mut self, init: bool) -> Self { - self.initialize_first_session = init; - self - } - pub fn offset(self, offset: BlockNumber) -> Self { - OFFSET.with(|v| *v.borrow_mut() = offset); - self - } - pub fn min_nominator_bond(mut self, amount: Balance) -> Self { - self.min_nominator_bond = amount; - self - } - pub fn min_validator_bond(mut self, amount: Balance) -> Self { - self.min_validator_bond = amount; - self - } - pub fn set_status(mut self, who: AccountId, status: StakerStatus) -> Self { - self.status.insert(who, status); - self - } - pub fn set_stake(mut self, who: AccountId, stake: Balance) -> Self { - self.stakes.insert(who, stake); - self - } - pub fn add_staker( - mut self, - stash: AccountId, - ctrl: AccountId, - stake: Balance, - status: StakerStatus, - ) -> Self { - self.stakers.push((stash, ctrl, stake, status)); - self - } - pub fn balance_factor(mut self, factor: Balance) -> Self { - self.balance_factor = factor; - self - } - fn build(self) -> sp_io::TestExternalities { - sp_tracing::try_init_simple(); - let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); - - let _ = pallet_balances::GenesisConfig:: { - balances: vec![ - (1, 10 * self.balance_factor), - (2, 20 * self.balance_factor), - (3, 300 * self.balance_factor), - (4, 400 * self.balance_factor), - // controllers (still used in some tests. Soon to be deprecated). - (10, self.balance_factor), - (20, self.balance_factor), - (30, self.balance_factor), - (40, self.balance_factor), - (50, self.balance_factor), - // stashes - (11, self.balance_factor * 1000), - (21, self.balance_factor * 2000), - (31, self.balance_factor * 2000), - (41, self.balance_factor * 2000), - (51, self.balance_factor * 2000), - // optional nominator - (100, self.balance_factor * 2000), - (101, self.balance_factor * 2000), - // aux accounts - (60, self.balance_factor), - (61, self.balance_factor * 2000), - (70, self.balance_factor), - (71, self.balance_factor * 2000), - (80, self.balance_factor), - (81, self.balance_factor * 2000), - // This allows us to have a total_payout different from 0. - (999, 1_000_000_000_000), - ], - } - .assimilate_storage(&mut storage); - - let mut stakers = vec![]; - if self.has_stakers { - stakers = vec![ - // (stash, ctrl, stake, status) - // these two will be elected in the default test where we elect 2. - (11, 11, self.balance_factor * 1000, StakerStatus::::Validator), - (21, 21, self.balance_factor * 1000, StakerStatus::::Validator), - // a loser validator - (31, 31, self.balance_factor * 500, StakerStatus::::Validator), - // an idle validator - (41, 41, self.balance_factor * 1000, StakerStatus::::Idle), - ]; - // optionally add a nominator - if self.nominate { - stakers.push(( - 101, - 101, - self.balance_factor * 500, - StakerStatus::::Nominator(vec![11, 21]), - )) - } - // replace any of the status if needed. - self.status.into_iter().for_each(|(stash, status)| { - let (_, _, _, ref mut prev_status) = stakers - .iter_mut() - .find(|s| s.0 == stash) - .expect("set_status staker should exist; qed"); - *prev_status = status; - }); - // replaced any of the stakes if needed. - self.stakes.into_iter().for_each(|(stash, stake)| { - let (_, _, ref mut prev_stake, _) = stakers - .iter_mut() - .find(|s| s.0 == stash) - .expect("set_stake staker should exits; qed."); - *prev_stake = stake; - }); - // extend stakers if needed. - stakers.extend(self.stakers) - } - - let _ = pallet_staking::GenesisConfig:: { - stakers: stakers.clone(), - validator_count: self.validator_count, - minimum_validator_count: self.minimum_validator_count, - invulnerables: self.invulnerables, - slash_reward_fraction: Perbill::from_percent(10), - min_nominator_bond: self.min_nominator_bond, - min_validator_bond: self.min_validator_bond, - ..Default::default() - } - .assimilate_storage(&mut storage); - - let _ = pallet_session::GenesisConfig:: { - keys: if self.has_stakers { - // set the keys for the first session. - stakers - .into_iter() - .map(|(id, ..)| (id, id, SessionKeys { other: id.into() })) - .collect() - } else { - // set some dummy validators in genesis. - (0..self.validator_count as u64) - .map(|id| (id, id, SessionKeys { other: id.into() })) - .collect() - }, - } - .assimilate_storage(&mut storage); - - let mut ext = sp_io::TestExternalities::from(storage); - - if self.initialize_first_session { - // We consider all test to start after timestamp is initialized This must be ensured by - // having `timestamp::on_initialize` called before `staking::on_initialize`. Also, if - // session length is 1, then it is already triggered. - ext.execute_with(|| { - System::set_block_number(1); - Session::on_initialize(1); - >::on_initialize(1); - Timestamp::set_timestamp(INIT_TIMESTAMP); - }); - } - - ext - } - pub fn build_and_execute(self, test: impl FnOnce() -> ()) { - sp_tracing::try_init_simple(); - let mut ext = self.build(); - ext.execute_with(test); - ext.execute_with(|| { - Staking::do_try_state(System::block_number()).unwrap(); - }); - } -} - -pub(crate) fn active_era() -> EraIndex { - Staking::active_era().unwrap().index -} - -pub(crate) fn current_era() -> EraIndex { - Staking::current_era().unwrap() -} - -pub(crate) fn bond(who: AccountId, val: Balance) { - let _ = Balances::make_free_balance_be(&who, val); - assert_ok!(Staking::bond(RuntimeOrigin::signed(who), val, RewardDestination::Controller)); -} - -pub(crate) fn bond_validator(who: AccountId, val: Balance) { - bond(who, val); - assert_ok!(Staking::validate(RuntimeOrigin::signed(who), ValidatorPrefs::default())); - assert_ok!(Session::set_keys( - RuntimeOrigin::signed(who), - SessionKeys { other: who.into() }, - vec![] - )); -} - -pub(crate) fn bond_nominator(who: AccountId, val: Balance, target: Vec) { - bond(who, val); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(who), target)); -} - -/// Progress to the given block, triggering session and era changes as we progress. -/// -/// This will finalize the previous block, initialize up to the given block, essentially simulating -/// a block import/propose process where we first initialize the block, then execute some stuff (not -/// in the function), and then finalize the block. -pub(crate) fn run_to_block(n: BlockNumber) { - Staking::on_finalize(System::block_number()); - for b in (System::block_number() + 1)..=n { - System::set_block_number(b); - Session::on_initialize(b); - >::on_initialize(b); - Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); - if b != n { - Staking::on_finalize(System::block_number()); - } - } -} - -/// Progresses from the current block number (whatever that may be) to the `P * session_index + 1`. -pub(crate) fn start_session(session_index: SessionIndex) { - let end: u64 = if Offset::get().is_zero() { - (session_index as u64) * Period::get() - } else { - Offset::get() + (session_index.saturating_sub(1) as u64) * Period::get() - }; - run_to_block(end); - // session must have progressed properly. - assert_eq!( - Session::current_index(), - session_index, - "current session index = {}, expected = {}", - Session::current_index(), - session_index, - ); -} - -/// Go one session forward. -pub(crate) fn advance_session() { - let current_index = Session::current_index(); - start_session(current_index + 1); -} - -/// Progress until the given era. -pub(crate) fn start_active_era(era_index: EraIndex) { - start_session((era_index * >::get()).into()); - assert_eq!(active_era(), era_index); - // One way or another, current_era must have changed before the active era, so they must match - // at this point. - assert_eq!(current_era(), active_era()); -} - -pub(crate) fn current_total_payout_for_duration(duration: u64) -> Balance { - let (payout, _rest) = ::EraPayout::era_payout( - Staking::eras_total_stake(active_era()), - Balances::total_issuance(), - duration, - ); - assert!(payout > 0); - payout -} - -pub(crate) fn maximum_payout_for_duration(duration: u64) -> Balance { - let (payout, rest) = ::EraPayout::era_payout( - Staking::eras_total_stake(active_era()), - Balances::total_issuance(), - duration, - ); - payout + rest -} - -/// Time it takes to finish a session. -/// -/// Note, if you see `time_per_session() - BLOCK_TIME`, it is fine. This is because we set the -/// timestamp after on_initialize, so the timestamp is always one block old. -pub(crate) fn time_per_session() -> u64 { - Period::get() * BLOCK_TIME -} - -/// Time it takes to finish an era. -/// -/// Note, if you see `time_per_era() - BLOCK_TIME`, it is fine. This is because we set the -/// timestamp after on_initialize, so the timestamp is always one block old. -pub(crate) fn time_per_era() -> u64 { - time_per_session() * SessionsPerEra::get() as u64 -} - -/// Time that will be calculated for the reward per era. -pub(crate) fn reward_time_per_era() -> u64 { - time_per_era() - BLOCK_TIME -} - -pub(crate) fn reward_all_elected() { - let rewards = ::SessionInterface::validators().into_iter().map(|v| (v, 1)); - - >::reward_by_ids(rewards) -} - -pub(crate) fn validator_controllers() -> Vec { - Session::validators() - .into_iter() - .map(|s| Staking::bonded(&s).expect("no controller for validator")) - .collect() -} - -pub(crate) fn on_offence_in_era( - offenders: &[OffenceDetails< - AccountId, - pallet_session::historical::IdentificationTuple, - >], - slash_fraction: &[Perbill], - era: EraIndex, - disable_strategy: DisableStrategy, -) { - let bonded_eras = crate::BondedEras::::get(); - for &(bonded_era, start_session) in bonded_eras.iter() { - if bonded_era == era { - let _ = Staking::on_offence(offenders, slash_fraction, start_session, disable_strategy); - return - } else if bonded_era > era { - break - } - } - - if Staking::active_era().unwrap().index == era { - let _ = Staking::on_offence( - offenders, - slash_fraction, - Staking::eras_start_session_index(era).unwrap(), - disable_strategy, - ); - } else { - panic!("cannot slash in era {}", era); - } -} - -pub(crate) fn on_offence_now( - offenders: &[OffenceDetails< - AccountId, - pallet_session::historical::IdentificationTuple, - >], - slash_fraction: &[Perbill], -) { - let now = Staking::active_era().unwrap().index; - on_offence_in_era(offenders, slash_fraction, now, DisableStrategy::WhenSlashed) -} - -pub(crate) fn add_slash(who: &AccountId) { - on_offence_now( - &[OffenceDetails { - offender: (*who, Staking::eras_stakers(active_era(), *who)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - ); -} - -/// Make all validator and nominator request their payment -pub(crate) fn make_all_reward_payment(era: EraIndex) { - let validators_with_reward = ErasRewardPoints::::get(era) - .individual - .keys() - .cloned() - .collect::>(); - - // reward validators - for validator_controller in validators_with_reward.iter().filter_map(Staking::bonded) { - let ledger = >::get(&validator_controller).unwrap(); - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), ledger.stash, era)); - } -} - -#[macro_export] -macro_rules! assert_session_era { - ($session:expr, $era:expr) => { - assert_eq!( - Session::current_index(), - $session, - "wrong session {} != {}", - Session::current_index(), - $session, - ); - assert_eq!( - Staking::current_era().unwrap(), - $era, - "wrong current era {} != {}", - Staking::current_era().unwrap(), - $era, - ); - }; -} - -pub(crate) fn staking_events() -> Vec> { - System::events() - .into_iter() - .map(|r| r.event) - .filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None }) - .collect() -} - -parameter_types! { - static StakingEventsIndex: usize = 0; -} -ord_parameter_types! { - pub const One: u64 = 1; -} - -type EnsureOneOrRoot = EitherOfDiverse, EnsureSignedBy>; - -pub(crate) fn staking_events_since_last_call() -> Vec> { - let all: Vec<_> = System::events() - .into_iter() - .filter_map(|r| if let RuntimeEvent::Staking(inner) = r.event { Some(inner) } else { None }) - .collect(); - let seen = StakingEventsIndex::get(); - StakingEventsIndex::set(all.len()); - all.into_iter().skip(seen).collect() -} - -pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) { - (Balances::free_balance(who), Balances::reserved_balance(who)) -} diff --git a/pallets/staking/src/pallet/impls.rs b/pallets/staking/src/pallet/impls.rs deleted file mode 100644 index fb5c27b15..000000000 --- a/pallets/staking/src/pallet/impls.rs +++ /dev/null @@ -1,1838 +0,0 @@ -// 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. - -//! Implementations for the Staking FRAME Pallet. - -use frame_election_provider_support::{ - data_provider, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ScoreProvider, - SortedListProvider, VoteWeight, VoterOf, -}; -#[cfg(feature = "try-runtime")] -use frame_support::ensure; -use frame_support::{ - defensive, - dispatch::WithPostDispatchInfo, - pallet_prelude::*, - traits::{ - Currency, Defensive, DefensiveResult, EstimateNextNewSession, Get, Imbalance, - LockableCurrency, OnUnbalanced, TryCollect, UnixTime, WithdrawReasons, - }, - weights::Weight, -}; -use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; -use pallet_session::historical; -#[cfg(any(test, feature = "try-runtime"))] -use sp_runtime::TryRuntimeError; -use sp_runtime::{ - traits::{Bounded, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero}, - Perbill, -}; -use sp_staking::{ - currency_to_vote::CurrencyToVote, - offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - EraIndex, SessionIndex, Stake, StakingInterface, -}; -use sp_std::prelude::*; - -use super::{pallet::*, STAKING_ID}; -use crate::{ - log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, Exposure, ExposureOf, - Forcing, IndividualExposure, MaxWinnersOf, Nominations, PositiveImbalanceOf, RewardDestination, - SessionInterface, StakingLedger, ValidatorPrefs, -}; - -/// The maximum number of iterations that we do whilst iterating over `T::VoterList` in -/// `get_npos_voters`. -/// -/// In most cases, if we want n items, we iterate exactly n times. In rare cases, if a voter is -/// invalid (for any reason) the iteration continues. With this constant, we iterate at most 2 * n -/// times and then give up. -const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; - -impl Pallet { - /// The total balance that can be slashed from a stash account as of right now. - pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { - // Weight note: consider making the stake accessible through stash. - Self::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default() - } - - /// Internal impl of [`Self::slashable_balance_of`] that returns [`VoteWeight`]. - pub fn slashable_balance_of_vote_weight( - stash: &T::AccountId, - issuance: BalanceOf, - ) -> VoteWeight { - T::CurrencyToVote::to_vote(Self::slashable_balance_of(stash), issuance) - } - - /// Returns a closure around `slashable_balance_of_vote_weight` that can be passed around. - /// - /// This prevents call sites from repeatedly requesting `total_issuance` from backend. But it is - /// important to be only used while the total issuance is not changing. - pub fn weight_of_fn() -> Box VoteWeight> { - // NOTE: changing this to unboxed `impl Fn(..)` return type and the pallet will still - // compile, while some types in mock fail to resolve. - let issuance = T::Currency::total_issuance(); - Box::new(move |who: &T::AccountId| -> VoteWeight { - Self::slashable_balance_of_vote_weight(who, issuance) - }) - } - - /// Same as `weight_of_fn`, but made for one time use. - pub fn weight_of(who: &T::AccountId) -> VoteWeight { - let issuance = T::Currency::total_issuance(); - Self::slashable_balance_of_vote_weight(who, issuance) - } - - pub(super) fn do_withdraw_unbonded( - controller: &T::AccountId, - num_slashing_spans: u32, - ) -> Result { - let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; - let (stash, old_total) = (ledger.stash.clone(), ledger.total); - if let Some(current_era) = Self::current_era() { - ledger = ledger.consolidate_unlocked(current_era) - } - - let used_weight = - if ledger.unlocking.is_empty() && ledger.active < T::Currency::minimum_balance() { - // This account must have called `unbond()` with some value that caused the active - // portion to fall below existential deposit + will have no more unlocking chunks - // left. We can now safely remove all staking-related information. - Self::kill_stash(&stash, num_slashing_spans)?; - // Remove the lock. - T::Currency::remove_lock(STAKING_ID, &stash); - - T::WeightInfo::withdraw_unbonded_kill(num_slashing_spans) - } else { - // This was the consequence of a partial unbond. just update the ledger and move on. - Self::update_ledger(&controller, &ledger); - - // This is only an update, so we use less overall weight. - T::WeightInfo::withdraw_unbonded_update(num_slashing_spans) - }; - - // `old_total` should never be less than the new total because - // `consolidate_unlocked` strictly subtracts balance. - if ledger.total < old_total { - // Already checked that this won't overflow by entry condition. - let value = old_total - ledger.total; - Self::deposit_event(Event::::Withdrawn { stash, amount: value }); - } - - Ok(used_weight) - } - - pub(super) fn do_payout_stakers( - validator_stash: T::AccountId, - era: EraIndex, - ) -> DispatchResultWithPostInfo { - // Validate input data - let current_era = CurrentEra::::get().ok_or_else(|| { - Error::::InvalidEraToReward - .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) - })?; - let history_depth = T::HistoryDepth::get(); - ensure!( - era <= current_era && era >= current_era.saturating_sub(history_depth), - Error::::InvalidEraToReward - .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) - ); - - // Note: if era has no reward to be claimed, era may be future. better not to update - // `ledger.claimed_rewards` in this case. - let era_payout = >::get(&era).ok_or_else(|| { - Error::::InvalidEraToReward - .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) - })?; - - let controller = Self::bonded(&validator_stash).ok_or_else(|| { - Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) - })?; - let mut ledger = >::get(&controller).ok_or(Error::::NotController)?; - - ledger - .claimed_rewards - .retain(|&x| x >= current_era.saturating_sub(history_depth)); - - match ledger.claimed_rewards.binary_search(&era) { - Ok(_) => - return Err(Error::::AlreadyClaimed - .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))), - Err(pos) => ledger - .claimed_rewards - .try_insert(pos, era) - // Since we retain era entries in `claimed_rewards` only upto - // `HistoryDepth`, following bound is always expected to be - // satisfied. - .defensive_map_err(|_| Error::::BoundNotMet)?, - } - - let exposure = >::get(&era, &ledger.stash); - - // Input data seems good, no errors allowed after this point - - >::insert(&controller, &ledger); - - // Get Era reward points. It has TOTAL and INDIVIDUAL - // Find the fraction of the era reward that belongs to the validator - // Take that fraction of the eras rewards to split to nominator and validator - // - // Then look at the validator, figure out the proportion of their reward - // which goes to them and each of their nominators. - - let era_reward_points = >::get(&era); - let total_reward_points = era_reward_points.total; - let validator_reward_points = era_reward_points - .individual - .get(&ledger.stash) - .copied() - .unwrap_or_else(Zero::zero); - - // Nothing to do if they have no reward points. - if validator_reward_points.is_zero() { - return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into()) - } - - // This is the fraction of the total reward that the validator and the - // nominators will get. - let validator_total_reward_part = - Perbill::from_rational(validator_reward_points, total_reward_points); - - // This is how much validator + nominators are entitled to. - let validator_total_payout = validator_total_reward_part * era_payout; - - let validator_prefs = Self::eras_validator_prefs(&era, &validator_stash); - // Validator first gets a cut off the top. - let validator_commission = validator_prefs.commission; - let validator_commission_payout = validator_commission * validator_total_payout; - - let validator_leftover_payout = validator_total_payout - validator_commission_payout; - // Now let's calculate how this is split to the validator. - let validator_exposure_part = Perbill::from_rational(exposure.own, exposure.total); - let validator_staking_payout = validator_exposure_part * validator_leftover_payout; - - Self::deposit_event(Event::::PayoutStarted { - era_index: era, - validator_stash: ledger.stash.clone(), - }); - - let mut total_imbalance = PositiveImbalanceOf::::zero(); - // We can now make total validator payout: - if let Some(imbalance) = - Self::make_payout(&ledger.stash, validator_staking_payout + validator_commission_payout) - { - Self::deposit_event(Event::::Rewarded { - stash: ledger.stash, - amount: imbalance.peek(), - }); - total_imbalance.subsume(imbalance); - } - - // Track the number of payout ops to nominators. Note: - // `WeightInfo::payout_stakers_alive_staked` always assumes at least a validator is paid - // out, so we do not need to count their payout op. - let mut nominator_payout_count: u32 = 0; - - // Lets now calculate how this is split to the nominators. - // Reward only the clipped exposures. Note this is not necessarily sorted. - for nominator in exposure.others.iter() { - let nominator_exposure_part = Perbill::from_rational(nominator.value, exposure.total); - - let nominator_reward: BalanceOf = - nominator_exposure_part * validator_leftover_payout; - // We can now make nominator payout: - if let Some(imbalance) = Self::make_payout(&nominator.who, nominator_reward) { - // Note: this logic does not count payouts for `RewardDestination::None`. - nominator_payout_count += 1; - let e = - Event::::Rewarded { stash: nominator.who.clone(), amount: imbalance.peek() }; - Self::deposit_event(e); - total_imbalance.subsume(imbalance); - } - } - - T::Reward::on_unbalanced(total_imbalance); - debug_assert!(nominator_payout_count <= T::MaxNominatorRewardedPerValidator::get()); - Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into()) - } - - /// Update the ledger for a controller. - /// - /// This will also update the stash lock. - pub(crate) fn update_ledger(controller: &T::AccountId, ledger: &StakingLedger) { - T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); - >::insert(controller, ledger); - } - - /// Chill a stash account. - pub(crate) fn chill_stash(stash: &T::AccountId) { - let chilled_as_validator = Self::do_remove_validator(stash); - let chilled_as_nominator = Self::do_remove_nominator(stash); - if chilled_as_validator || chilled_as_nominator { - Self::deposit_event(Event::::Chilled { stash: stash.clone() }); - } - } - - /// Actually make a payment to a staker. This uses the currency's reward function - /// to pay the right payee for the given staker account. - fn make_payout(stash: &T::AccountId, amount: BalanceOf) -> Option> { - let dest = Self::payee(stash); - match dest { - RewardDestination::Controller => Self::bonded(stash) - .map(|controller| T::Currency::deposit_creating(&controller, amount)), - RewardDestination::Stash => T::Currency::deposit_into_existing(stash, amount).ok(), - RewardDestination::Staked => Self::bonded(stash) - .and_then(|c| Self::ledger(&c).map(|l| (c, l))) - .and_then(|(controller, mut l)| { - l.active += amount; - l.total += amount; - let r = T::Currency::deposit_into_existing(stash, amount).ok(); - Self::update_ledger(&controller, &l); - r - }), - RewardDestination::Account(dest_account) => - Some(T::Currency::deposit_creating(&dest_account, amount)), - RewardDestination::None => None, - } - } - - /// Plan a new session potentially trigger a new era. - fn new_session( - session_index: SessionIndex, - is_genesis: bool, - ) -> Option>> { - if let Some(current_era) = Self::current_era() { - // Initial era has been set. - let current_era_start_session_index = Self::eras_start_session_index(current_era) - .unwrap_or_else(|| { - frame_support::print("Error: start_session_index must be set for current_era"); - 0 - }); - - let era_length = session_index.saturating_sub(current_era_start_session_index); // Must never happen. - - match ForceEra::::get() { - // Will be set to `NotForcing` again if a new era has been triggered. - Forcing::ForceNew => (), - // Short circuit to `try_trigger_new_era`. - Forcing::ForceAlways => (), - // Only go to `try_trigger_new_era` if deadline reached. - Forcing::NotForcing if era_length >= T::SessionsPerEra::get() => (), - _ => { - // Either `Forcing::ForceNone`, - // or `Forcing::NotForcing if era_length >= T::SessionsPerEra::get()`. - return None - }, - } - - // New era. - let maybe_new_era_validators = Self::try_trigger_new_era(session_index, is_genesis); - if maybe_new_era_validators.is_some() && - matches!(ForceEra::::get(), Forcing::ForceNew) - { - Self::set_force_era(Forcing::NotForcing); - } - - maybe_new_era_validators - } else { - // Set initial era. - log!(debug, "Starting the first era."); - Self::try_trigger_new_era(session_index, is_genesis) - } - } - - /// Start a session potentially starting an era. - fn start_session(start_session: SessionIndex) { - let next_active_era = Self::active_era().map(|e| e.index + 1).unwrap_or(0); - // This is only `Some` when current era has already progressed to the next era, while the - // active era is one behind (i.e. in the *last session of the active era*, or *first session - // of the new current era*, depending on how you look at it). - if let Some(next_active_era_start_session_index) = - Self::eras_start_session_index(next_active_era) - { - if next_active_era_start_session_index == start_session { - Self::start_era(start_session); - } else if next_active_era_start_session_index < start_session { - // This arm should never happen, but better handle it than to stall the staking - // pallet. - frame_support::print("Warning: A session appears to have been skipped."); - Self::start_era(start_session); - } - } - - // disable all offending validators that have been disabled for the whole era - for (index, disabled) in >::get() { - if disabled { - T::SessionInterface::disable_validator(index); - } - } - } - - /// End a session potentially ending an era. - fn end_session(session_index: SessionIndex) { - if let Some(active_era) = Self::active_era() { - if let Some(next_active_era_start_session_index) = - Self::eras_start_session_index(active_era.index + 1) - { - if next_active_era_start_session_index == session_index + 1 { - Self::end_era(active_era, session_index); - } - } - } - } - - /// Start a new era. It does: - /// - /// * Increment `active_era.index`, - /// * reset `active_era.start`, - /// * update `BondedEras` and apply slashes. - fn start_era(start_session: SessionIndex) { - let active_era = ActiveEra::::mutate(|active_era| { - let new_index = active_era.as_ref().map(|info| info.index + 1).unwrap_or(0); - *active_era = Some(ActiveEraInfo { - index: new_index, - // Set new active era start in next `on_finalize`. To guarantee usage of `Time` - start: None, - }); - new_index - }); - - let bonding_duration = T::BondingDuration::get(); - - BondedEras::::mutate(|bonded| { - bonded.push((active_era, start_session)); - - if active_era > bonding_duration { - let first_kept = active_era - bonding_duration; - - // Prune out everything that's from before the first-kept index. - let n_to_prune = - bonded.iter().take_while(|&&(era_idx, _)| era_idx < first_kept).count(); - - // Kill slashing metadata. - for (pruned_era, _) in bonded.drain(..n_to_prune) { - slashing::clear_era_metadata::(pruned_era); - } - - if let Some(&(_, first_session)) = bonded.first() { - T::SessionInterface::prune_historical_up_to(first_session); - } - } - }); - - Self::apply_unapplied_slashes(active_era); - } - - /// Compute payout for era. - fn end_era(active_era: ActiveEraInfo, _session_index: SessionIndex) { - // Note: active_era_start can be None if end era is called during genesis config. - if let Some(active_era_start) = active_era.start { - let now_as_millis_u64 = T::UnixTime::now().as_millis().saturated_into::(); - - let era_duration = (now_as_millis_u64 - active_era_start).saturated_into::(); - let staked = Self::eras_total_stake(&active_era.index); - let issuance = T::Currency::total_issuance(); - let (validator_payout, remainder) = - T::EraPayout::era_payout(staked, issuance, era_duration); - - Self::deposit_event(Event::::EraPaid { - era_index: active_era.index, - validator_payout, - remainder, - }); - - // Set ending era reward. - >::insert(&active_era.index, validator_payout); - T::RewardRemainder::on_unbalanced(T::Currency::issue(remainder)); - - // Clear offending validators. - >::kill(); - } - } - - /// Plan a new era. - /// - /// * Bump the current era storage (which holds the latest planned era). - /// * Store start session index for the new planned era. - /// * Clean old era information. - /// * Store staking information for the new planned era - /// - /// Returns the new validator set. - pub fn trigger_new_era( - start_session_index: SessionIndex, - exposures: BoundedVec< - (T::AccountId, Exposure>), - MaxWinnersOf, - >, - ) -> BoundedVec> { - // Increment or set current era. - let new_planned_era = CurrentEra::::mutate(|s| { - *s = Some(s.map(|s| s + 1).unwrap_or(0)); - s.unwrap() - }); - ErasStartSessionIndex::::insert(&new_planned_era, &start_session_index); - - // Clean old era information. - if let Some(old_era) = new_planned_era.checked_sub(T::HistoryDepth::get() + 1) { - Self::clear_era_information(old_era); - } - - // Set staking information for the new era. - Self::store_stakers_info(exposures, new_planned_era) - } - - /// Potentially plan a new era. - /// - /// Get election result from `T::ElectionProvider`. - /// In case election result has more than [`MinimumValidatorCount`] validator trigger a new era. - /// - /// In case a new era is planned, the new validator set is returned. - pub(crate) fn try_trigger_new_era( - start_session_index: SessionIndex, - is_genesis: bool, - ) -> Option>> { - let election_result: BoundedVec<_, MaxWinnersOf> = if is_genesis { - let result = ::elect().map_err(|e| { - log!(warn, "genesis election provider failed due to {:?}", e); - Self::deposit_event(Event::StakingElectionFailed); - }); - - result - .ok()? - .into_inner() - .try_into() - // both bounds checked in integrity test to be equal - .defensive_unwrap_or_default() - } else { - let result = ::elect().map_err(|e| { - log!(warn, "election provider failed due to {:?}", e); - Self::deposit_event(Event::StakingElectionFailed); - }); - result.ok()? - }; - - let exposures = Self::collect_exposures(election_result); - if (exposures.len() as u32) < Self::minimum_validator_count().max(1) { - // Session will panic if we ever return an empty validator set, thus max(1) ^^. - match CurrentEra::::get() { - Some(current_era) if current_era > 0 => log!( - warn, - "chain does not have enough staking candidates to operate for era {:?} ({} \ - elected, minimum is {})", - CurrentEra::::get().unwrap_or(0), - exposures.len(), - Self::minimum_validator_count(), - ), - None => { - // The initial era is allowed to have no exposures. - // In this case the SessionManager is expected to choose a sensible validator - // set. - // TODO: this should be simplified #8911 - CurrentEra::::put(0); - ErasStartSessionIndex::::insert(&0, &start_session_index); - }, - _ => (), - } - - Self::deposit_event(Event::StakingElectionFailed); - return None - } - - Self::deposit_event(Event::StakersElected); - Some(Self::trigger_new_era(start_session_index, exposures)) - } - - /// Process the output of the election. - /// - /// Store staking information for the new planned era - pub fn store_stakers_info( - exposures: BoundedVec< - (T::AccountId, Exposure>), - MaxWinnersOf, - >, - new_planned_era: EraIndex, - ) -> BoundedVec> { - let elected_stashes: BoundedVec<_, MaxWinnersOf> = exposures - .iter() - .cloned() - .map(|(x, _)| x) - .collect::>() - .try_into() - .expect("since we only map through exposures, size of elected_stashes is always same as exposures; qed"); - - // Populate stakers, exposures, and the snapshot of validator prefs. - let mut total_stake: BalanceOf = Zero::zero(); - exposures.into_iter().for_each(|(stash, exposure)| { - total_stake = total_stake.saturating_add(exposure.total); - >::insert(new_planned_era, &stash, &exposure); - - let mut exposure_clipped = exposure; - let clipped_max_len = T::MaxNominatorRewardedPerValidator::get() as usize; - if exposure_clipped.others.len() > clipped_max_len { - exposure_clipped.others.sort_by(|a, b| a.value.cmp(&b.value).reverse()); - exposure_clipped.others.truncate(clipped_max_len); - } - >::insert(&new_planned_era, &stash, exposure_clipped); - }); - - // Insert current era staking information - >::insert(&new_planned_era, total_stake); - - // Collect the pref of all winners. - for stash in &elected_stashes { - let pref = Self::validators(stash); - >::insert(&new_planned_era, stash, pref); - } - - if new_planned_era > 0 { - log!( - info, - "new validator set of size {:?} has been processed for era {:?}", - elected_stashes.len(), - new_planned_era, - ); - } - - elected_stashes - } - - /// Consume a set of [`BoundedSupports`] from [`sp_npos_elections`] and collect them into a - /// [`Exposure`]. - fn collect_exposures( - supports: BoundedSupportsOf, - ) -> BoundedVec<(T::AccountId, Exposure>), MaxWinnersOf> { - let total_issuance = T::Currency::total_issuance(); - let to_currency = |e: frame_election_provider_support::ExtendedBalance| { - T::CurrencyToVote::to_currency(e, total_issuance) - }; - - supports - .into_iter() - .map(|(validator, support)| { - // Build `struct exposure` from `support`. - let mut others = Vec::with_capacity(support.voters.len()); - let mut own: BalanceOf = Zero::zero(); - let mut total: BalanceOf = Zero::zero(); - support - .voters - .into_iter() - .map(|(nominator, weight)| (nominator, to_currency(weight))) - .for_each(|(nominator, stake)| { - if nominator == validator { - own = own.saturating_add(stake); - } else { - others.push(IndividualExposure { who: nominator, value: stake }); - } - total = total.saturating_add(stake); - }); - - let exposure = Exposure { own, others, total }; - (validator, exposure) - }) - .try_collect() - .expect("we only map through support vector which cannot change the size; qed") - } - - /// 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). - pub(crate) fn kill_stash(stash: &T::AccountId, num_slashing_spans: u32) -> DispatchResult { - let controller = >::get(stash).ok_or(Error::::NotStash)?; - - slashing::clear_stash_metadata::(stash, num_slashing_spans)?; - - >::remove(stash); - >::remove(&controller); - - >::remove(stash); - Self::do_remove_validator(stash); - Self::do_remove_nominator(stash); - - frame_system::Pallet::::dec_consumers(stash); - - Ok(()) - } - - /// Clear all era information for given era. - pub(crate) fn clear_era_information(era_index: EraIndex) { - let mut cursor = >::clear_prefix(era_index, u32::MAX, None); - debug_assert!(cursor.maybe_cursor.is_none()); - cursor = >::clear_prefix(era_index, u32::MAX, None); - debug_assert!(cursor.maybe_cursor.is_none()); - cursor = >::clear_prefix(era_index, u32::MAX, None); - debug_assert!(cursor.maybe_cursor.is_none()); - >::remove(era_index); - >::remove(era_index); - >::remove(era_index); - ErasStartSessionIndex::::remove(era_index); - } - - /// Apply previously-unapplied slashes on the beginning of a new era, after a delay. - fn apply_unapplied_slashes(active_era: EraIndex) { - let era_slashes = UnappliedSlashes::::take(&active_era); - log!( - debug, - "found {} slashes scheduled to be executed in era {:?}", - era_slashes.len(), - active_era, - ); - for slash in era_slashes { - let slash_era = active_era.saturating_sub(T::SlashDeferDuration::get()); - slashing::apply_slash::(slash, slash_era); - } - } - - /// Add reward points to validators using their stash 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 fn reward_by_ids(validators_points: impl IntoIterator) { - if let Some(active_era) = Self::active_era() { - >::mutate(active_era.index, |era_rewards| { - for (validator, points) in validators_points.into_iter() { - *era_rewards.individual.entry(validator).or_default() += points; - era_rewards.total += points; - } - }); - } - } - - /// Helper to set a new `ForceEra` mode. - pub(crate) fn set_force_era(mode: Forcing) { - log!(info, "Setting force era mode {:?}.", mode); - ForceEra::::put(mode); - Self::deposit_event(Event::::ForceEra { mode }); - } - - /// Ensures that at the end of the current session there will be a new era. - pub(crate) fn ensure_new_era() { - match ForceEra::::get() { - Forcing::ForceAlways | Forcing::ForceNew => (), - _ => Self::set_force_era(Forcing::ForceNew), - } - } - - #[cfg(feature = "runtime-benchmarks")] - pub fn add_era_stakers( - current_era: EraIndex, - stash: T::AccountId, - exposure: Exposure>, - ) { - >::insert(¤t_era, &stash, &exposure); - } - - #[cfg(feature = "runtime-benchmarks")] - pub fn set_slash_reward_fraction(fraction: Perbill) { - SlashRewardFraction::::put(fraction); - } - - /// Get all of the voters that are eligible for the npos election. - /// - /// `maybe_max_len` can imposes a cap on the number of voters returned; - /// - /// Sets `MinimumActiveStake` to the minimum active nominator stake in the returned set of - /// nominators. - /// - /// This function is self-weighing as [`DispatchClass::Mandatory`]. - pub fn get_npos_voters(maybe_max_len: Option) -> Vec> { - let max_allowed_len = { - let all_voter_count = T::VoterList::count() as usize; - maybe_max_len.unwrap_or(all_voter_count).min(all_voter_count) - }; - - let mut all_voters = Vec::<_>::with_capacity(max_allowed_len); - - // cache a few things. - let weight_of = Self::weight_of_fn(); - - let mut voters_seen = 0u32; - let mut validators_taken = 0u32; - let mut nominators_taken = 0u32; - let mut min_active_stake = u64::MAX; - - let mut sorted_voters = T::VoterList::iter(); - while all_voters.len() < max_allowed_len && - voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * max_allowed_len as u32) - { - let voter = match sorted_voters.next() { - Some(voter) => { - voters_seen.saturating_inc(); - voter - }, - None => break, - }; - - if let Some(Nominations { targets, .. }) = >::get(&voter) { - let voter_weight = weight_of(&voter); - if !targets.is_empty() { - all_voters.push((voter.clone(), voter_weight, targets)); - nominators_taken.saturating_inc(); - } else { - // Technically should never happen, but not much we can do about it. - } - min_active_stake = - if voter_weight < min_active_stake { voter_weight } else { min_active_stake }; - } else if Validators::::contains_key(&voter) { - // if this voter is a validator: - let self_vote = ( - voter.clone(), - weight_of(&voter), - vec![voter.clone()] - .try_into() - .expect("`MaxVotesPerVoter` must be greater than or equal to 1"), - ); - all_voters.push(self_vote); - validators_taken.saturating_inc(); - } else { - // this can only happen if: 1. there a bug in the bags-list (or whatever is the - // sorted list) logic and the state of the two pallets is no longer compatible, or - // because the nominators is not decodable since they have more nomination than - // `T::MaxNominations`. The latter can rarely happen, and is not really an emergency - // or bug if it does. - log!( - warn, - "DEFENSIVE: invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now", - voter - ); - } - } - - // all_voters should have not re-allocated. - debug_assert!(all_voters.capacity() == max_allowed_len); - - Self::register_weight(T::WeightInfo::get_npos_voters(validators_taken, nominators_taken)); - - let min_active_stake: T::CurrencyBalance = - if all_voters.len() == 0 { 0u64.into() } else { min_active_stake.into() }; - - MinimumActiveStake::::put(min_active_stake); - - log!( - info, - "generated {} npos voters, {} from validators and {} nominators", - all_voters.len(), - validators_taken, - nominators_taken - ); - - all_voters - } - - /// Get the targets for an upcoming npos election. - /// - /// This function is self-weighing as [`DispatchClass::Mandatory`]. - pub fn get_npos_targets(maybe_max_len: Option) -> Vec { - let max_allowed_len = maybe_max_len.unwrap_or_else(|| T::TargetList::count() as usize); - let mut all_targets = Vec::::with_capacity(max_allowed_len); - let mut targets_seen = 0; - - let mut targets_iter = T::TargetList::iter(); - while all_targets.len() < max_allowed_len && - targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * max_allowed_len as u32) - { - let target = match targets_iter.next() { - Some(target) => { - targets_seen.saturating_inc(); - target - }, - None => break, - }; - - if Validators::::contains_key(&target) { - all_targets.push(target); - } - } - - Self::register_weight(T::WeightInfo::get_npos_targets(all_targets.len() as u32)); - log!(info, "generated {} npos targets", all_targets.len()); - - all_targets - } - - /// This function will add a nominator to the `Nominators` storage map, - /// and `VoterList`. - /// - /// If the nominator already exists, their nominations will be updated. - /// - /// NOTE: you must ALWAYS use this function to add nominator or update their targets. Any access - /// to `Nominators` or `VoterList` outside of this function is almost certainly - /// wrong. - pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { - if !Nominators::::contains_key(who) { - // maybe update sorted list. - let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) - .defensive_unwrap_or_default(); - } - Nominators::::insert(who, nominations); - - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::VoterList::count() - ); - } - - /// This function will remove a nominator from the `Nominators` storage map, - /// and `VoterList`. - /// - /// Returns true if `who` was removed from `Nominators`, otherwise false. - /// - /// NOTE: you must ALWAYS use this function to remove a nominator from the system. Any access to - /// `Nominators` or `VoterList` outside of this function is almost certainly - /// wrong. - pub fn do_remove_nominator(who: &T::AccountId) -> bool { - let outcome = if Nominators::::contains_key(who) { - Nominators::::remove(who); - let _ = T::VoterList::on_remove(who).defensive(); - true - } else { - false - }; - - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::VoterList::count() - ); - - outcome - } - - /// This function will add a validator to the `Validators` storage map. - /// - /// If the validator already exists, their preferences will be updated. - /// - /// NOTE: you must ALWAYS use this function to add a validator to the system. Any access to - /// `Validators` or `VoterList` outside of this function is almost certainly - /// wrong. - pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { - if !Validators::::contains_key(who) { - // maybe update sorted list. - let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) - .defensive_unwrap_or_default(); - } - Validators::::insert(who, prefs); - - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::VoterList::count() - ); - } - - /// This function will remove a validator from the `Validators` storage map. - /// - /// Returns true if `who` was removed from `Validators`, otherwise false. - /// - /// NOTE: you must ALWAYS use this function to remove a validator from the system. Any access to - /// `Validators` or `VoterList` outside of this function is almost certainly - /// wrong. - pub fn do_remove_validator(who: &T::AccountId) -> bool { - let outcome = if Validators::::contains_key(who) { - Validators::::remove(who); - let _ = T::VoterList::on_remove(who).defensive(); - true - } else { - false - }; - - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::VoterList::count() - ); - - outcome - } - - /// Register some amount of weight directly with the system pallet. - /// - /// This is always mandatory weight. - fn register_weight(weight: Weight) { - >::register_extra_weight_unchecked( - weight, - DispatchClass::Mandatory, - ); - } -} - -impl Pallet { - /// Returns the current nominations quota for nominators. - /// - /// Used by the runtime API. - /// Note: for now, this api runtime will always return value of `T::MaxNominations` and thus it - /// is redundant. However, with the upcoming changes in - /// , the nominations quota will change - /// depending on the nominators balance. We're introducing this runtime API now to prepare the - /// community to use it before rolling out PR#12970. - pub fn api_nominations_quota(_balance: BalanceOf) -> u32 { - T::MaxNominations::get() - } -} - -impl ElectionDataProvider for Pallet { - type AccountId = T::AccountId; - type BlockNumber = BlockNumberFor; - type MaxVotesPerVoter = T::MaxNominations; - - fn desired_targets() -> data_provider::Result { - Self::register_weight(T::DbWeight::get().reads(1)); - Ok(Self::validator_count()) - } - - fn electing_voters(maybe_max_len: Option) -> data_provider::Result>> { - // This can never fail -- if `maybe_max_len` is `Some(_)` we handle it. - let voters = Self::get_npos_voters(maybe_max_len); - debug_assert!(maybe_max_len.map_or(true, |max| voters.len() <= max)); - - Ok(voters) - } - - fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { - let target_count = T::TargetList::count(); - - // We can't handle this case yet -- return an error. - if maybe_max_len.map_or(false, |max_len| target_count > max_len as u32) { - return Err("Target snapshot too big") - } - - Ok(Self::get_npos_targets(None)) - } - - fn next_election_prediction(now: BlockNumberFor) -> BlockNumberFor { - let current_era = Self::current_era().unwrap_or(0); - let current_session = Self::current_planned_session(); - let current_era_start_session_index = - Self::eras_start_session_index(current_era).unwrap_or(0); - // Number of session in the current era or the maximum session per era if reached. - let era_progress = current_session - .saturating_sub(current_era_start_session_index) - .min(T::SessionsPerEra::get()); - - let until_this_session_end = T::NextNewSession::estimate_next_new_session(now) - .0 - .unwrap_or_default() - .saturating_sub(now); - - let session_length = T::NextNewSession::average_session_length(); - - let sessions_left: BlockNumberFor = match ForceEra::::get() { - Forcing::ForceNone => Bounded::max_value(), - Forcing::ForceNew | Forcing::ForceAlways => Zero::zero(), - Forcing::NotForcing if era_progress >= T::SessionsPerEra::get() => Zero::zero(), - Forcing::NotForcing => T::SessionsPerEra::get() - .saturating_sub(era_progress) - // One session is computed in this_session_end. - .saturating_sub(1) - .into(), - }; - - now.saturating_add( - until_this_session_end.saturating_add(sessions_left.saturating_mul(session_length)), - ) - } - - #[cfg(feature = "runtime-benchmarks")] - fn add_voter( - voter: T::AccountId, - weight: VoteWeight, - targets: BoundedVec, - ) { - let stake = >::try_from(weight).unwrap_or_else(|_| { - panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.") - }); - >::insert(voter.clone(), voter.clone()); - >::insert( - voter.clone(), - StakingLedger { - stash: voter.clone(), - active: stake, - total: stake, - unlocking: Default::default(), - claimed_rewards: Default::default(), - }, - ); - - Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false }); - } - - #[cfg(feature = "runtime-benchmarks")] - fn add_target(target: T::AccountId) { - let stake = MinValidatorBond::::get() * 100u32.into(); - >::insert(target.clone(), target.clone()); - >::insert( - target.clone(), - StakingLedger { - stash: target.clone(), - active: stake, - total: stake, - unlocking: Default::default(), - claimed_rewards: Default::default(), - }, - ); - Self::do_add_validator( - &target, - ValidatorPrefs { commission: Perbill::zero(), blocked: false }, - ); - } - - #[cfg(feature = "runtime-benchmarks")] - fn clear() { - #[allow(deprecated)] - >::remove_all(None); - #[allow(deprecated)] - >::remove_all(None); - #[allow(deprecated)] - >::remove_all(); - #[allow(deprecated)] - >::remove_all(); - - T::VoterList::unsafe_clear(); - } - - #[cfg(feature = "runtime-benchmarks")] - fn put_snapshot( - voters: Vec>, - targets: Vec, - target_stake: Option, - ) { - targets.into_iter().for_each(|v| { - let stake: BalanceOf = target_stake - .and_then(|w| >::try_from(w).ok()) - .unwrap_or_else(|| MinNominatorBond::::get() * 100u32.into()); - >::insert(v.clone(), v.clone()); - >::insert( - v.clone(), - StakingLedger { - stash: v.clone(), - active: stake, - total: stake, - unlocking: Default::default(), - claimed_rewards: Default::default(), - }, - ); - Self::do_add_validator( - &v, - ValidatorPrefs { commission: Perbill::zero(), blocked: false }, - ); - }); - - voters.into_iter().for_each(|(v, s, t)| { - let stake = >::try_from(s).unwrap_or_else(|_| { - panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.") - }); - >::insert(v.clone(), v.clone()); - >::insert( - v.clone(), - StakingLedger { - stash: v.clone(), - active: stake, - total: stake, - unlocking: Default::default(), - claimed_rewards: Default::default(), - }, - ); - Self::do_add_nominator( - &v, - Nominations { targets: t, submitted_in: 0, suppressed: false }, - ); - }); - } -} - -/// 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, though -/// some session can lag in between the newest session planned and the latest session started. -impl pallet_session::SessionManager for Pallet { - fn new_session(new_index: SessionIndex) -> Option> { - log!(trace, "planning new session {}", new_index); - CurrentPlannedSession::::put(new_index); - Self::new_session(new_index, false).map(|v| v.into_inner()) - } - fn new_session_genesis(new_index: SessionIndex) -> Option> { - log!(trace, "planning new session {} at genesis", new_index); - CurrentPlannedSession::::put(new_index); - Self::new_session(new_index, true).map(|v| v.into_inner()) - } - fn start_session(start_index: SessionIndex) { - log!(trace, "starting session {}", start_index); - Self::start_session(start_index) - } - fn end_session(end_index: SessionIndex) { - log!(trace, "ending session {}", end_index); - Self::end_session(end_index) - } -} - -impl historical::SessionManager>> - for Pallet -{ - fn new_session( - new_index: SessionIndex, - ) -> Option>)>> { - >::new_session(new_index).map(|validators| { - let current_era = Self::current_era() - // Must be some as a new era has been created. - .unwrap_or(0); - - validators - .into_iter() - .map(|v| { - let exposure = Self::eras_stakers(current_era, &v); - (v, exposure) - }) - .collect() - }) - } - fn new_session_genesis( - new_index: SessionIndex, - ) -> Option>)>> { - >::new_session_genesis(new_index).map( - |validators| { - let current_era = Self::current_era() - // Must be some as a new era has been created. - .unwrap_or(0); - - validators - .into_iter() - .map(|v| { - let exposure = Self::eras_stakers(current_era, &v); - (v, exposure) - }) - .collect() - }, - ) - } - fn start_session(start_index: SessionIndex) { - >::start_session(start_index) - } - fn end_session(end_index: SessionIndex) { - >::end_session(end_index) - } -} - -/// Add reward points to block authors: -/// * 20 points to the block producer for producing a (non-uncle) block, -impl pallet_authorship::EventHandler> for Pallet -where - T: Config + pallet_authorship::Config + pallet_session::Config, -{ - fn note_author(author: T::AccountId) { - Self::reward_by_ids(vec![(author, 20)]) - } -} - -/// 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 = Exposure<::AccountId, BalanceOf>, - FullIdentificationOf = ExposureOf, - >, - T::SessionHandler: pallet_session::SessionHandler<::AccountId>, - T::SessionManager: pallet_session::SessionManager<::AccountId>, - T::ValidatorIdOf: Convert< - ::AccountId, - Option<::AccountId>, - >, -{ - fn on_offence( - offenders: &[OffenceDetails< - T::AccountId, - pallet_session::historical::IdentificationTuple, - >], - slash_fraction: &[Perbill], - slash_session: SessionIndex, - disable_strategy: DisableStrategy, - ) -> Weight { - let reward_proportion = SlashRewardFraction::::get(); - let mut consumed_weight = Weight::from_parts(0, 0); - let mut add_db_reads_writes = |reads, writes| { - consumed_weight += T::DbWeight::get().reads_writes(reads, writes); - }; - - let active_era = { - let active_era = Self::active_era(); - add_db_reads_writes(1, 0); - if active_era.is_none() { - // This offence need not be re-submitted. - return consumed_weight - } - active_era.expect("value checked not to be `None`; qed").index - }; - let active_era_start_session_index = Self::eras_start_session_index(active_era) - .unwrap_or_else(|| { - frame_support::print("Error: start_session_index must be set for current_era"); - 0 - }); - add_db_reads_writes(1, 0); - - let window_start = active_era.saturating_sub(T::BondingDuration::get()); - - // Fast path for active-era report - most likely. - // `slash_session` cannot be in a future active era. It must be in `active_era` or before. - let slash_era = if slash_session >= active_era_start_session_index { - active_era - } else { - let eras = BondedEras::::get(); - add_db_reads_writes(1, 0); - - // Reverse because it's more likely to find reports from recent eras. - match eras.iter().rev().find(|&(_, sesh)| sesh <= &slash_session) { - Some((slash_era, _)) => *slash_era, - // Before bonding period. defensive - should be filtered out. - None => return consumed_weight, - } - }; - - add_db_reads_writes(1, 1); - - let slash_defer_duration = T::SlashDeferDuration::get(); - - let invulnerables = Self::invulnerables(); - add_db_reads_writes(1, 0); - - for (details, slash_fraction) in offenders.iter().zip(slash_fraction) { - let (stash, exposure) = &details.offender; - - // Skip if the validator is invulnerable. - if invulnerables.contains(stash) { - continue - } - - let unapplied = slashing::compute_slash::(slashing::SlashParams { - stash, - slash: *slash_fraction, - exposure, - slash_era, - window_start, - now: active_era, - reward_proportion, - disable_strategy, - }); - - Self::deposit_event(Event::::SlashReported { - validator: stash.clone(), - fraction: *slash_fraction, - slash_era, - }); - - 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, slash_era); - { - 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. - log!( - debug, - "deferring slash of {:?}% happened in {:?} (reported in {:?}) to {:?}", - slash_fraction, - slash_era, - active_era, - slash_era + slash_defer_duration + 1, - ); - UnappliedSlashes::::mutate( - slash_era.saturating_add(slash_defer_duration).saturating_add(One::one()), - move |for_later| for_later.push(unapplied), - ); - add_db_reads_writes(1, 1); - } - } else { - add_db_reads_writes(4 /* fetch_spans */, 5 /* kick_out_if_recent */) - } - } - - consumed_weight - } -} - -impl ScoreProvider for Pallet { - type Score = VoteWeight; - - fn score(who: &T::AccountId) -> Self::Score { - Self::weight_of(who) - } - - #[cfg(feature = "runtime-benchmarks")] - fn set_score_of(who: &T::AccountId, weight: Self::Score) { - // this will clearly results in an inconsistent state, but it should not matter for a - // benchmark. - let active: BalanceOf = weight.try_into().map_err(|_| ()).unwrap(); - let mut ledger = match Self::ledger(who) { - None => StakingLedger::default_from(who.clone()), - Some(l) => l, - }; - ledger.active = active; - - >::insert(who, ledger); - >::insert(who, who); - - // also, we play a trick to make sure that a issuance based-`CurrencyToVote` behaves well: - // This will make sure that total issuance is zero, thus the currency to vote will be a 1-1 - // conversion. - let imbalance = T::Currency::burn(T::Currency::total_issuance()); - // kinda ugly, but gets the job done. The fact that this works here is a HUGE exception. - // Don't try this pattern in other places. - sp_std::mem::forget(imbalance); - } -} - -/// A simple sorted list implementation that does not require any additional pallets. Note, this -/// does not provide validators in sorted order. If you desire nominators in a sorted order take -/// a look at [`pallet-bags-list`]. -pub struct UseValidatorsMap(sp_std::marker::PhantomData); -impl SortedListProvider for UseValidatorsMap { - type Score = BalanceOf; - type Error = (); - - /// Returns iterator over voter list, which can have `take` called on it. - fn iter() -> Box> { - Box::new(Validators::::iter().map(|(v, _)| v)) - } - fn iter_from( - start: &T::AccountId, - ) -> Result>, Self::Error> { - if Validators::::contains_key(start) { - let start_key = Validators::::hashed_key_for(start); - Ok(Box::new(Validators::::iter_from(start_key).map(|(n, _)| n))) - } else { - Err(()) - } - } - fn count() -> u32 { - Validators::::count() - } - fn contains(id: &T::AccountId) -> bool { - Validators::::contains_key(id) - } - fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { - // nothing to do on insert. - Ok(()) - } - fn get_score(id: &T::AccountId) -> Result { - Ok(Pallet::::weight_of(id).into()) - } - fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { - // nothing to do on update. - Ok(()) - } - fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> { - // nothing to do on remove. - Ok(()) - } - fn unsafe_regenerate( - _: impl IntoIterator, - _: Box Self::Score>, - ) -> u32 { - // nothing to do upon regenerate. - 0 - } - #[cfg(feature = "try-runtime")] - fn try_state() -> Result<(), TryRuntimeError> { - Ok(()) - } - - fn unsafe_clear() { - #[allow(deprecated)] - Validators::::remove_all(); - } - - #[cfg(feature = "runtime-benchmarks")] - fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score { - unimplemented!() - } -} - -/// A simple voter list implementation that does not require any additional pallets. Note, this -/// does not provided nominators in sorted ordered. If you desire nominators in a sorted order take -/// a look at [`pallet-bags-list]. -pub struct UseNominatorsAndValidatorsMap(sp_std::marker::PhantomData); -impl SortedListProvider for UseNominatorsAndValidatorsMap { - type Error = (); - type Score = VoteWeight; - - fn iter() -> Box> { - Box::new( - Validators::::iter() - .map(|(v, _)| v) - .chain(Nominators::::iter().map(|(n, _)| n)), - ) - } - fn iter_from( - start: &T::AccountId, - ) -> Result>, Self::Error> { - if Validators::::contains_key(start) { - let start_key = Validators::::hashed_key_for(start); - Ok(Box::new( - Validators::::iter_from(start_key) - .map(|(n, _)| n) - .chain(Nominators::::iter().map(|(x, _)| x)), - )) - } else if Nominators::::contains_key(start) { - let start_key = Nominators::::hashed_key_for(start); - Ok(Box::new(Nominators::::iter_from(start_key).map(|(n, _)| n))) - } else { - Err(()) - } - } - fn count() -> u32 { - Nominators::::count().saturating_add(Validators::::count()) - } - fn contains(id: &T::AccountId) -> bool { - Nominators::::contains_key(id) || Validators::::contains_key(id) - } - fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { - // nothing to do on insert. - Ok(()) - } - fn get_score(id: &T::AccountId) -> Result { - Ok(Pallet::::weight_of(id)) - } - fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { - // nothing to do on update. - Ok(()) - } - fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> { - // nothing to do on remove. - Ok(()) - } - fn unsafe_regenerate( - _: impl IntoIterator, - _: Box Self::Score>, - ) -> u32 { - // nothing to do upon regenerate. - 0 - } - - #[cfg(feature = "try-runtime")] - fn try_state() -> Result<(), TryRuntimeError> { - Ok(()) - } - - fn unsafe_clear() { - // NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a - // condition of SortedListProvider::unsafe_clear. - #[allow(deprecated)] - Nominators::::remove_all(); - #[allow(deprecated)] - Validators::::remove_all(); - } - - #[cfg(feature = "runtime-benchmarks")] - fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score { - unimplemented!() - } -} - -impl StakingInterface for Pallet { - type AccountId = T::AccountId; - type Balance = BalanceOf; - type CurrencyToVote = T::CurrencyToVote; - - fn minimum_nominator_bond() -> Self::Balance { - MinNominatorBond::::get() - } - - fn minimum_validator_bond() -> Self::Balance { - MinValidatorBond::::get() - } - - fn desired_validator_count() -> u32 { - ValidatorCount::::get() - } - - fn election_ongoing() -> bool { - T::ElectionProvider::ongoing() - } - - fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult { - let num_slashing_spans = Self::slashing_spans(&who).map_or(0, |s| s.iter().count() as u32); - Self::force_unstake(RawOrigin::Root.into(), who.clone(), num_slashing_spans) - } - - fn stash_by_ctrl(controller: &Self::AccountId) -> Result { - Self::ledger(controller) - .map(|l| l.stash) - .ok_or(Error::::NotController.into()) - } - - fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool { - ErasStakers::::iter_prefix(era).any(|(validator, exposures)| { - validator == *who || exposures.others.iter().any(|i| i.who == *who) - }) - } - - fn bonding_duration() -> EraIndex { - T::BondingDuration::get() - } - - fn current_era() -> EraIndex { - Self::current_era().unwrap_or(Zero::zero()) - } - - fn stake(who: &Self::AccountId) -> Result>, DispatchError> { - Self::bonded(who) - .and_then(|c| Self::ledger(c)) - .map(|l| Stake { total: l.total, active: l.active }) - .ok_or(Error::::NotStash.into()) - } - - fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { - Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra) - } - - fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult { - let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; - Self::unbond(RawOrigin::Signed(ctrl).into(), value) - .map_err(|with_post| with_post.error) - .map(|_| ()) - } - - fn chill(who: &Self::AccountId) -> DispatchResult { - // defensive-only: any account bonded via this interface has the stash set as the - // controller, but we have to be sure. Same comment anywhere else that we read this. - let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; - Self::chill(RawOrigin::Signed(ctrl).into()) - } - - fn withdraw_unbonded( - who: Self::AccountId, - num_slashing_spans: u32, - ) -> Result { - let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; - Self::withdraw_unbonded(RawOrigin::Signed(ctrl.clone()).into(), num_slashing_spans) - .map(|_| !Ledger::::contains_key(&ctrl)) - .map_err(|with_post| with_post.error) - } - - fn bond( - who: &Self::AccountId, - value: Self::Balance, - payee: &Self::AccountId, - ) -> DispatchResult { - Self::bond( - RawOrigin::Signed(who.clone()).into(), - value, - RewardDestination::Account(payee.clone()), - ) - } - - fn nominate(who: &Self::AccountId, targets: Vec) -> DispatchResult { - let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; - let targets = targets.into_iter().map(T::Lookup::unlookup).collect::>(); - Self::nominate(RawOrigin::Signed(ctrl).into(), targets) - } - - fn status( - who: &Self::AccountId, - ) -> Result, DispatchError> { - let is_bonded = Self::bonded(who).is_some(); - if !is_bonded { - return Err(Error::::NotStash.into()) - } - - let is_validator = Validators::::contains_key(&who); - let is_nominator = Nominators::::get(&who); - - use sp_staking::StakerStatus; - match (is_validator, is_nominator.is_some()) { - (false, false) => Ok(StakerStatus::Idle), - (true, false) => Ok(StakerStatus::Validator), - (false, true) => Ok(StakerStatus::Nominator( - is_nominator.expect("is checked above; qed").targets.into_inner(), - )), - (true, true) => { - defensive!("cannot be both validators and nominator"); - Err(Error::::BadState.into()) - }, - } - } - - sp_staking::runtime_benchmarks_enabled! { - fn nominations(who: &Self::AccountId) -> Option> { - Nominators::::get(who).map(|n| n.targets.into_inner()) - } - - fn add_era_stakers( - current_era: &EraIndex, - stash: &T::AccountId, - exposures: Vec<(Self::AccountId, Self::Balance)>, - ) { - let others = exposures - .iter() - .map(|(who, value)| IndividualExposure { who: who.clone(), value: value.clone() }) - .collect::>(); - let exposure = Exposure { total: Default::default(), own: Default::default(), others }; - >::insert(¤t_era, &stash, &exposure); - } - - fn set_current_era(era: EraIndex) { - CurrentEra::::put(era); - } - } -} - -#[cfg(any(test, feature = "try-runtime"))] -impl Pallet { - pub(crate) fn do_try_state(_: BlockNumberFor) -> Result<(), TryRuntimeError> { - ensure!( - T::VoterList::iter() - .all(|x| >::contains_key(&x) || >::contains_key(&x)), - "VoterList contains non-staker" - ); - - Self::check_nominators()?; - Self::check_exposures()?; - Self::check_ledgers()?; - Self::check_count() - } - - fn check_count() -> Result<(), TryRuntimeError> { - ensure!( - ::VoterList::count() == - Nominators::::count() + Validators::::count(), - "wrong external count" - ); - ensure!( - ::TargetList::count() == Validators::::count(), - "wrong external count" - ); - ensure!( - ValidatorCount::::get() <= - ::MaxWinners::get(), - Error::::TooManyValidators - ); - Ok(()) - } - - fn check_ledgers() -> Result<(), TryRuntimeError> { - Bonded::::iter() - .map(|(_, ctrl)| Self::ensure_ledger_consistent(ctrl)) - .collect::, _>>()?; - Ok(()) - } - - fn check_exposures() -> Result<(), TryRuntimeError> { - // a check per validator to ensure the exposure struct is always sane. - let era = Self::active_era().unwrap().index; - ErasStakers::::iter_prefix_values(era) - .map(|expo| { - ensure!( - expo.total == - expo.own + - expo.others - .iter() - .map(|e| e.value) - .fold(Zero::zero(), |acc, x| acc + x), - "wrong total exposure.", - ); - Ok(()) - }) - .collect::>() - } - - fn check_nominators() -> Result<(), TryRuntimeError> { - // a check per nominator to ensure their entire stake is correctly distributed. Will only - // kick-in if the nomination was submitted before the current era. - let era = Self::active_era().unwrap().index; - >::iter() - .filter_map( - |(nominator, nomination)| { - if nomination.submitted_in < era { - Some(nominator) - } else { - None - } - }, - ) - .map(|nominator| -> Result<(), TryRuntimeError> { - // must be bonded. - Self::ensure_is_stash(&nominator)?; - let mut sum = BalanceOf::::zero(); - T::SessionInterface::validators() - .iter() - .map(|v| Self::eras_stakers(era, v)) - .map(|e| -> Result<(), TryRuntimeError> { - let individual = - e.others.iter().filter(|e| e.who == nominator).collect::>(); - let len = individual.len(); - match len { - 0 => { /* not supporting this validator at all. */ }, - 1 => sum += individual[0].value, - _ => - return Err( - "nominator cannot back a validator more than once.".into() - ), - }; - Ok(()) - }) - .collect::, _>>()?; - Ok(()) - }) - .collect::, _>>()?; - - Ok(()) - } - - fn ensure_is_stash(who: &T::AccountId) -> Result<(), &'static str> { - ensure!(Self::bonded(who).is_some(), "Not a stash."); - Ok(()) - } - - fn ensure_ledger_consistent(ctrl: T::AccountId) -> Result<(), TryRuntimeError> { - // ensures ledger.total == ledger.active + sum(ledger.unlocking). - let ledger = Self::ledger(ctrl.clone()).ok_or("Not a controller.")?; - let real_total: BalanceOf = - ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value); - ensure!(real_total == ledger.total, "ledger.total corrupt"); - - if !(ledger.active >= T::Currency::minimum_balance() || ledger.active.is_zero()) { - log!(warn, "ledger.active less than ED: {:?}, {:?}", ctrl, ledger) - } - - Ok(()) - } -} diff --git a/pallets/staking/src/pallet/mod.rs b/pallets/staking/src/pallet/mod.rs deleted file mode 100644 index 46d5403ee..000000000 --- a/pallets/staking/src/pallet/mod.rs +++ /dev/null @@ -1,1781 +0,0 @@ -// 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 frame_election_provider_support::{ - ElectionProvider, ElectionProviderBase, SortedListProvider, VoteWeight, -}; -use frame_support::{ - dispatch::Codec, - pallet_prelude::*, - traits::{ - Currency, Defensive, DefensiveResult, DefensiveSaturating, EnsureOrigin, - EstimateNextNewSession, Get, LockIdentifier, LockableCurrency, OnUnbalanced, TryCollect, - UnixTime, - }, - weights::Weight, - BoundedVec, -}; -use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; -use sp_runtime::{ - traits::{CheckedSub, SaturatedConversion, StaticLookup, Zero}, - ArithmeticError, Perbill, Percent, -}; -use sp_staking::{EraIndex, SessionIndex}; -use sp_std::prelude::*; - -mod impls; - -pub use impls::*; - -use crate::{ - slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, EraPayout, - EraRewardPoints, Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, - RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, - ValidatorPrefs, -}; - -const STAKING_ID: LockIdentifier = *b"staking "; -// The speculative number of spans are used as an input of the weight annotation of -// [`Call::unbond`], as the post dipatch 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 super::*; - use crate::BenchmarkingConfig; - - /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(13); - - #[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, - >; - /// 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, - >; - - /// Maximum number of nominations per nominator. - #[pallet::constant] - type MaxNominations: Get; - - /// 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`, - /// `StakingLedger.claimed_rewards`. - /// - /// 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.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 number of nominators rewarded for each validator. - /// - /// For each validator only the `$MaxNominatorRewardedPerValidator` biggest stakers can - /// claim their reward. This used to limit the i/o cost for the nominator payout. - #[pallet::constant] - type MaxNominatorRewardedPerValidator: Get; - - /// The fraction of the validator set that is safe to be offending. - /// After the threshold is reached a new era will be forced. - type OffendingValidatorsThreshold: 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; - - /// Something that listens to staking updates and performs actions based on the data it - /// receives. - /// - /// WARNING: this only reports slashing events for the time being. - type EventListeners: sp_staking::OnStakingUpdate>; - - /// 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] - #[pallet::getter(fn bonded)] - 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. - #[pallet::storage] - #[pallet::getter(fn ledger)] - 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] - #[pallet::getter(fn payee)] - pub type Payee = - StorageMap<_, Twox64Concat, T::AccountId, RewardDestination, ValueQuery>; - - /// 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 - /// [`Config::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>; - - /// 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 `HISTORY_DEPTH` 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 `HISTORY_DEPTH` eras. - /// If stakers hasn't been set or has been removed then empty exposure is returned. - #[pallet::storage] - #[pallet::getter(fn eras_stakers)] - #[pallet::unbounded] - pub type ErasStakers = StorageDoubleMap< - _, - Twox64Concat, - EraIndex, - Twox64Concat, - T::AccountId, - Exposure>, - ValueQuery, - >; - - /// Clipped Exposure of validator at era. - /// - /// This is similar to [`ErasStakers`] but number of nominators exposed is reduced to the - /// `T::MaxNominatorRewardedPerValidator` 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. - /// - /// Is it removed after `HISTORY_DEPTH` eras. - /// If stakers hasn't been set or has been removed then empty exposure is returned. - #[pallet::storage] - #[pallet::unbounded] - #[pallet::getter(fn eras_stakers_clipped)] - pub type ErasStakersClipped = StorageDoubleMap< - _, - Twox64Concat, - EraIndex, - Twox64Concat, - T::AccountId, - Exposure>, - 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 `HISTORY_DEPTH` 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 `HISTORY_DEPTH` 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 `HISTORY_DEPTH` 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 `HISTORY_DEPTH` 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>; - - /// 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 and whether they are currently - /// disabled. - /// - /// This value should be a superset of disabled validators since not all offences lead to the - /// validator being disabled (if there was no slash). This is needed to track the percentage of - /// validators that have offended in the current era, ensuring a new era is forced if - /// `OffendingValidatorsThreshold` is reached. The vec is always kept sorted so that we can find - /// whether a given validator has previously offended using binary search. It gets cleared when - /// the era ends. - #[pallet::storage] - #[pallet::unbounded] - #[pallet::getter(fn offending_validators)] - pub type OffendingValidators = StorageValue<_, Vec<(u32, bool)>, 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. - Rewarded { stash: T::AccountId, 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 }, - /// A new force era mode was set. - ForceEra { mode: Forcing }, - } - - #[pallet::error] - 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, - /// 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, - } - - #[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!( - T::MaxNominations::get(), - ::MaxVotesPerVoter::get() - ); - // and that MaxNominations is always greater than 1, since we count on this. - assert!(!T::MaxNominations::get().is_zero()); - - // ensure election results are always bounded with the same value - assert!( - ::MaxWinners::get() == - ::MaxWinners::get() - ); - - sp_std::if_std! { - sp_io::TestExternalities::new_empty().execute_with(|| - 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)?; - let controller_to_be_deprecated = stash.clone(); - - if >::contains_key(&stash) { - return Err(Error::::AlreadyBonded.into()) - } - - if >::contains_key(&controller_to_be_deprecated) { - 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)?; - - // You're auto-bonded forever, here. We might improve this by only bonding when - // you actually validate/nominate and remove once you unbond __everything__. - >::insert(&stash, &stash); - >::insert(&stash, payee); - - let current_era = CurrentEra::::get().unwrap_or(0); - let history_depth = T::HistoryDepth::get(); - let last_reward_era = current_era.saturating_sub(history_depth); - - 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 item = StakingLedger { - stash: stash.clone(), - total: value, - active: value, - unlocking: Default::default(), - claimed_rewards: (last_reward_era..current_era) - .try_collect() - // Since last_reward_era is calculated as `current_era - - // HistoryDepth`, following bound is always expected to be - // satisfied. - .defensive_map_err(|_| Error::::BoundNotMet)?, - }; - Self::update_ledger(&controller_to_be_deprecated, &item); - 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)?; - - let controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; - let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; - - let stash_balance = T::Currency::free_balance(&stash); - if let Some(extra) = stash_balance.checked_sub(&ledger.total) { - let extra = extra.min(max_additional); - ledger.total += extra; - ledger.active += extra; - // Last check: the new active amount of ledger must be more than ED. - ensure!( - ledger.active >= T::Currency::minimum_balance(), - Error::::InsufficientBond - ); - - // NOTE: ledger must be updated prior to calling `Self::weight_of`. - Self::update_ledger(&controller, &ledger); - // 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(&ledger.stash)).defensive(); - } - - Self::deposit_event(Event::::Bonded { stash, amount: extra }); - } - Ok(()) - } - - /// 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) - .map(|l| l.unlocking.len()) - .ok_or(Error::::NotController)?; - - // 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).ok_or(Error::::NotController)?; - let mut value = value.min(ledger.active); - - 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(&ledger.stash) { - MinNominatorBond::::get() - } else if Validators::::contains_key(&ledger.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) + 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`. - Self::update_ledger(&controller, &ledger); - - // update this staker in the sorted list, if they exist in it. - if T::VoterList::contains(&ledger.stash) { - let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) - .defensive(); - } - - Self::deposit_event(Event::::Unbonded { stash: ledger.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 propotional 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).ok_or(Error::::NotController)?; - - 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(&controller).ok_or(Error::::NotController)?; - 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::MaxNominations::get() 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(&controller).ok_or(Error::::NotController)?; - 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).ok_or(Error::::NotController)?; - let stash = &ledger.stash; - >::insert(stash, payee); - 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)?; - let old_controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; - - if >::contains_key(&stash) { - return Err(Error::::AlreadyPaired.into()) - } - if old_controller != stash { - >::insert(&stash, &stash); - if let Some(l) = >::take(&old_controller) { - >::insert(&stash, l); - } - } - 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 upto 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 upto 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. - Self::kill_stash(&stash, num_slashing_spans)?; - - // Remove the lock. - T::Currency::remove_lock(STAKING_ID, &stash); - 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 all the stakers behind a single validator for a single era. - /// - /// - `validator_stash` is the stash account of the validator. Their nominators, up to - /// `T::MaxNominatorRewardedPerValidator`, will also receive their rewards. - /// - `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. - /// - /// ## Complexity - /// - At most O(MaxNominatorRewardedPerValidator). - #[pallet::call_index(18)] - #[pallet::weight(T::WeightInfo::payout_stakers_alive_staked( - T::MaxNominatorRewardedPerValidator::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).ok_or(Error::::NotController)?; - 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, - }); - - // NOTE: ledger must be updated prior to calling `Self::weight_of`. - Self::update_ledger(&controller, &ledger); - if T::VoterList::contains(&ledger.stash) { - let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) - .defensive(); - } - - let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed - .saturating_add(initial_unlocking) - .saturating_sub(ledger.unlocking.len() 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(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) - .map(|l| l.total) - .unwrap_or_default() < ed; - ensure!(reapable, Error::::FundedTarget); - - Self::kill_stash(&stash, num_slashing_spans)?; - T::Currency::remove_lock(STAKING_ID, &stash); - - 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).ok_or(Error::::NotController)?; - 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, - ) -> 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); - 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, controller: T::AccountId) -> DispatchResult { - // Anyone can call this function. - let caller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; - let stash = ledger.stash; - - // 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(()) - } - } -} - -/// 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]) -} diff --git a/pallets/staking/src/slashing.rs b/pallets/staking/src/slashing.rs deleted file mode 100644 index 1315b27bc..000000000 --- a/pallets/staking/src/slashing.rs +++ /dev/null @@ -1,861 +0,0 @@ -// 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. - -//! A slashing implementation for NPoS systems. -//! -//! For the purposes of the economic model, it is easiest to think of each validator as a nominator -//! which nominates only its own identity. -//! -//! The act of nomination signals intent to unify economic identity with the validator - to take -//! part in the rewards of a job well done, and to take part in the punishment of a job done badly. -//! -//! There are 3 main difficulties to account for with slashing in NPoS: -//! - A nominator can nominate multiple validators and be slashed via any of them. -//! - Until slashed, stake is reused from era to era. Nominating with N coins for E eras in a row -//! does not mean you have N*E coins to be slashed - you've only ever had N. -//! - Slashable offences can be found after the fact and out of order. -//! -//! The algorithm implemented in this module tries to balance these 3 difficulties. -//! -//! First, we only slash participants for the _maximum_ slash they receive in some time period, -//! rather than the sum. This ensures a protection from overslashing. -//! -//! Second, we do not want the time period (or "span") that the maximum is computed -//! over to last indefinitely. That would allow participants to begin acting with -//! impunity after some point, fearing no further repercussions. For that reason, we -//! automatically "chill" validators and withdraw a nominator's nomination after a slashing event, -//! requiring them to re-enlist voluntarily (acknowledging the slash) and begin a new -//! slashing span. -//! -//! Typically, you will have a single slashing event per slashing span. Only in the case -//! where a validator releases many misbehaviors at once, or goes "back in time" to misbehave in -//! eras that have already passed, would you encounter situations where a slashing span -//! has multiple misbehaviors. However, accounting for such cases is necessary -//! to deter a class of "rage-quit" attacks. -//! -//! Based on research at - -use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{ - ensure, - traits::{Currency, Defensive, Get, Imbalance, OnUnbalanced}, -}; -use scale_info::TypeInfo; -use sp_runtime::{ - traits::{Saturating, Zero}, - DispatchResult, RuntimeDebug, -}; -use sp_staking::{offence::DisableStrategy, EraIndex}; -use sp_std::vec::Vec; - -use crate::{ - BalanceOf, Config, Error, Exposure, NegativeImbalanceOf, NominatorSlashInEra, - OffendingValidators, Pallet, Perbill, SessionInterface, SpanSlash, UnappliedSlash, - ValidatorSlashInEra, -}; - -/// The proportion of the slashing reward to be paid out on the first slashing detection. -/// This is f_1 in the paper. -const REWARD_F1: Perbill = Perbill::from_percent(50); - -/// The index of a slashing span - unique to each stash. -pub type SpanIndex = u32; - -// A range of start..end eras for a slashing span. -#[derive(Encode, Decode, TypeInfo)] -#[cfg_attr(test, derive(Debug, PartialEq))] -pub(crate) struct SlashingSpan { - pub(crate) index: SpanIndex, - pub(crate) start: EraIndex, - pub(crate) length: Option, // the ongoing slashing span has indeterminate length. -} - -impl SlashingSpan { - fn contains_era(&self, era: EraIndex) -> bool { - self.start <= era && self.length.map_or(true, |l| self.start + l > era) - } -} - -/// An encoding of all of a nominator's slashing spans. -#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct SlashingSpans { - // the index of the current slashing span of the nominator. different for - // every stash, resets when the account hits free balance 0. - span_index: SpanIndex, - // the start era of the most recent (ongoing) slashing span. - last_start: EraIndex, - // the last era at which a non-zero slash occurred. - last_nonzero_slash: EraIndex, - // all prior slashing spans' start indices, in reverse order (most recent first) - // encoded as offsets relative to the slashing span after it. - prior: Vec, -} - -impl SlashingSpans { - // creates a new record of slashing spans for a stash, starting at the beginning - // of the bonding period, relative to now. - pub(crate) fn new(window_start: EraIndex) -> Self { - SlashingSpans { - span_index: 0, - last_start: window_start, - // initialize to zero, as this structure is lazily created until - // the first slash is applied. setting equal to `window_start` would - // put a time limit on nominations. - last_nonzero_slash: 0, - prior: Vec::new(), - } - } - - // update the slashing spans to reflect the start of a new span at the era after `now` - // returns `true` if a new span was started, `false` otherwise. `false` indicates - // that internal state is unchanged. - pub(crate) fn end_span(&mut self, now: EraIndex) -> bool { - let next_start = now + 1; - if next_start <= self.last_start { - return false - } - - let last_length = next_start - self.last_start; - self.prior.insert(0, last_length); - self.last_start = next_start; - self.span_index += 1; - true - } - - // an iterator over all slashing spans in _reverse_ order - most recent first. - pub(crate) fn iter(&'_ self) -> impl Iterator + '_ { - let mut last_start = self.last_start; - let mut index = self.span_index; - let last = SlashingSpan { index, start: last_start, length: None }; - let prior = self.prior.iter().cloned().map(move |length| { - let start = last_start - length; - last_start = start; - index -= 1; - - SlashingSpan { index, start, length: Some(length) } - }); - - sp_std::iter::once(last).chain(prior) - } - - /// Yields the era index where the most recent non-zero slash occurred. - pub fn last_nonzero_slash(&self) -> EraIndex { - self.last_nonzero_slash - } - - // prune the slashing spans against a window, whose start era index is given. - // - // If this returns `Some`, then it includes a range start..end of all the span - // indices which were pruned. - fn prune(&mut self, window_start: EraIndex) -> Option<(SpanIndex, SpanIndex)> { - let old_idx = self - .iter() - .skip(1) // skip ongoing span. - .position(|span| span.length.map_or(false, |len| span.start + len <= window_start)); - - let earliest_span_index = self.span_index - self.prior.len() as SpanIndex; - let pruned = match old_idx { - Some(o) => { - self.prior.truncate(o); - let new_earliest = self.span_index - self.prior.len() as SpanIndex; - Some((earliest_span_index, new_earliest)) - }, - None => None, - }; - - // readjust the ongoing span, if it started before the beginning of the window. - self.last_start = sp_std::cmp::max(self.last_start, window_start); - pruned - } -} - -/// A slashing-span record for a particular stash. -#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)] -pub(crate) struct SpanRecord { - slashed: Balance, - paid_out: Balance, -} - -impl SpanRecord { - /// The value of stash balance slashed in this span. - #[cfg(test)] - pub(crate) fn amount(&self) -> &Balance { - &self.slashed - } -} - -/// Parameters for performing a slash. -#[derive(Clone)] -pub(crate) struct SlashParams<'a, T: 'a + Config> { - /// The stash account being slashed. - pub(crate) stash: &'a T::AccountId, - /// The proportion of the slash. - pub(crate) slash: Perbill, - /// The exposure of the stash and all nominators. - pub(crate) exposure: &'a Exposure>, - /// The era where the offence occurred. - pub(crate) slash_era: EraIndex, - /// The first era in the current bonding period. - pub(crate) window_start: EraIndex, - /// The current era. - pub(crate) now: EraIndex, - /// The maximum percentage of a slash that ever gets paid out. - /// This is f_inf in the paper. - pub(crate) reward_proportion: Perbill, - /// When to disable offenders. - pub(crate) disable_strategy: DisableStrategy, -} - -/// Computes a slash of a validator and nominators. It returns an unapplied -/// record to be applied at some later point. Slashing metadata is updated in storage, -/// since unapplied records are only rarely intended to be dropped. -/// -/// The pending slash record returned does not have initialized reporters. Those have -/// to be set at a higher level, if any. -pub(crate) fn compute_slash( - params: SlashParams, -) -> Option>> { - let mut reward_payout = Zero::zero(); - let mut val_slashed = Zero::zero(); - - // is the slash amount here a maximum for the era? - let own_slash = params.slash * params.exposure.own; - if params.slash * params.exposure.total == Zero::zero() { - // kick out the validator even if they won't be slashed, - // as long as the misbehavior is from their most recent slashing span. - kick_out_if_recent::(params); - return None - } - - let prior_slash_p = ValidatorSlashInEra::::get(¶ms.slash_era, params.stash) - .map_or(Zero::zero(), |(prior_slash_proportion, _)| prior_slash_proportion); - - // compare slash proportions rather than slash values to avoid issues due to rounding - // error. - if params.slash.deconstruct() > prior_slash_p.deconstruct() { - ValidatorSlashInEra::::insert( - ¶ms.slash_era, - params.stash, - &(params.slash, own_slash), - ); - } else { - // we slash based on the max in era - this new event is not the max, - // so neither the validator or any nominators will need an update. - // - // this does lead to a divergence of our system from the paper, which - // pays out some reward even if the latest report is not max-in-era. - // we opt to avoid the nominator lookups and edits and leave more rewards - // for more drastic misbehavior. - return None - } - - // apply slash to validator. - { - let mut spans = fetch_spans::( - params.stash, - params.window_start, - &mut reward_payout, - &mut val_slashed, - params.reward_proportion, - ); - - let target_span = spans.compare_and_update_span_slash(params.slash_era, own_slash); - - if target_span == Some(spans.span_index()) { - // misbehavior occurred within the current slashing span - take appropriate - // actions. - - // chill the validator - it misbehaved in the current span and should - // not continue in the next election. also end the slashing span. - spans.end_span(params.now); - >::chill_stash(params.stash); - } - } - - let disable_when_slashed = params.disable_strategy != DisableStrategy::Never; - add_offending_validator::(params.stash, disable_when_slashed); - - let mut nominators_slashed = Vec::new(); - reward_payout += slash_nominators::(params.clone(), prior_slash_p, &mut nominators_slashed); - - Some(UnappliedSlash { - validator: params.stash.clone(), - own: val_slashed, - others: nominators_slashed, - reporters: Vec::new(), - payout: reward_payout, - }) -} - -// doesn't apply any slash, but kicks out the validator if the misbehavior is from -// the most recent slashing span. -fn kick_out_if_recent(params: SlashParams) { - // these are not updated by era-span or end-span. - let mut reward_payout = Zero::zero(); - let mut val_slashed = Zero::zero(); - let mut spans = fetch_spans::( - params.stash, - params.window_start, - &mut reward_payout, - &mut val_slashed, - params.reward_proportion, - ); - - if spans.era_span(params.slash_era).map(|s| s.index) == Some(spans.span_index()) { - spans.end_span(params.now); - >::chill_stash(params.stash); - } - - let disable_without_slash = params.disable_strategy == DisableStrategy::Always; - add_offending_validator::(params.stash, disable_without_slash); -} - -/// Add the given validator to the offenders list and optionally disable it. -/// If after adding the validator `OffendingValidatorsThreshold` is reached -/// a new era will be forced. -fn add_offending_validator(stash: &T::AccountId, disable: bool) { - OffendingValidators::::mutate(|offending| { - let validators = T::SessionInterface::validators(); - let validator_index = match validators.iter().position(|i| i == stash) { - Some(index) => index, - None => return, - }; - - let validator_index_u32 = validator_index as u32; - - match offending.binary_search_by_key(&validator_index_u32, |(index, _)| *index) { - // this is a new offending validator - Err(index) => { - offending.insert(index, (validator_index_u32, disable)); - - let offending_threshold = - T::OffendingValidatorsThreshold::get() * validators.len() as u32; - - if offending.len() >= offending_threshold as usize { - // force a new era, to select a new validator set - >::ensure_new_era() - } - - if disable { - T::SessionInterface::disable_validator(validator_index_u32); - } - }, - Ok(index) => { - if disable && !offending[index].1 { - // the validator had previously offended without being disabled, - // let's make sure we disable it now - offending[index].1 = true; - T::SessionInterface::disable_validator(validator_index_u32); - } - }, - } - }); -} - -/// Slash nominators. Accepts general parameters and the prior slash percentage of the validator. -/// -/// Returns the amount of reward to pay out. -fn slash_nominators( - params: SlashParams, - prior_slash_p: Perbill, - nominators_slashed: &mut Vec<(T::AccountId, BalanceOf)>, -) -> BalanceOf { - let mut reward_payout = Zero::zero(); - - nominators_slashed.reserve(params.exposure.others.len()); - for nominator in ¶ms.exposure.others { - let stash = &nominator.who; - let mut nom_slashed = Zero::zero(); - - // the era slash of a nominator always grows, if the validator - // had a new max slash for the era. - let era_slash = { - let own_slash_prior = prior_slash_p * nominator.value; - let own_slash_by_validator = params.slash * nominator.value; - let own_slash_difference = own_slash_by_validator.saturating_sub(own_slash_prior); - - let mut era_slash = - NominatorSlashInEra::::get(¶ms.slash_era, stash).unwrap_or_else(Zero::zero); - era_slash += own_slash_difference; - NominatorSlashInEra::::insert(¶ms.slash_era, stash, &era_slash); - - era_slash - }; - - // compare the era slash against other eras in the same span. - { - let mut spans = fetch_spans::( - stash, - params.window_start, - &mut reward_payout, - &mut nom_slashed, - params.reward_proportion, - ); - - let target_span = spans.compare_and_update_span_slash(params.slash_era, era_slash); - - if target_span == Some(spans.span_index()) { - // end the span, but don't chill the nominator. - spans.end_span(params.now); - } - } - nominators_slashed.push((stash.clone(), nom_slashed)); - } - - reward_payout -} - -// helper struct for managing a set of spans we are currently inspecting. -// writes alterations to disk on drop, but only if a slash has been carried out. -// -// NOTE: alterations to slashing metadata should not be done after this is dropped. -// dropping this struct applies any necessary slashes, which can lead to free balance -// being 0, and the account being garbage-collected -- a dead account should get no new -// metadata. -struct InspectingSpans<'a, T: Config + 'a> { - dirty: bool, - window_start: EraIndex, - stash: &'a T::AccountId, - spans: SlashingSpans, - paid_out: &'a mut BalanceOf, - slash_of: &'a mut BalanceOf, - reward_proportion: Perbill, - _marker: sp_std::marker::PhantomData, -} - -// fetches the slashing spans record for a stash account, initializing it if necessary. -fn fetch_spans<'a, T: Config + 'a>( - stash: &'a T::AccountId, - window_start: EraIndex, - paid_out: &'a mut BalanceOf, - slash_of: &'a mut BalanceOf, - reward_proportion: Perbill, -) -> InspectingSpans<'a, T> { - let spans = crate::SlashingSpans::::get(stash).unwrap_or_else(|| { - let spans = SlashingSpans::new(window_start); - crate::SlashingSpans::::insert(stash, &spans); - spans - }); - - InspectingSpans { - dirty: false, - window_start, - stash, - spans, - slash_of, - paid_out, - reward_proportion, - _marker: sp_std::marker::PhantomData, - } -} - -impl<'a, T: 'a + Config> InspectingSpans<'a, T> { - fn span_index(&self) -> SpanIndex { - self.spans.span_index - } - - fn end_span(&mut self, now: EraIndex) { - self.dirty = self.spans.end_span(now) || self.dirty; - } - - // add some value to the slash of the staker. - // invariant: the staker is being slashed for non-zero value here - // although `amount` may be zero, as it is only a difference. - fn add_slash(&mut self, amount: BalanceOf, slash_era: EraIndex) { - *self.slash_of += amount; - self.spans.last_nonzero_slash = sp_std::cmp::max(self.spans.last_nonzero_slash, slash_era); - } - - // find the span index of the given era, if covered. - fn era_span(&self, era: EraIndex) -> Option { - self.spans.iter().find(|span| span.contains_era(era)) - } - - // compares the slash in an era to the overall current span slash. - // if it's higher, applies the difference of the slashes and then updates the span on disk. - // - // returns the span index of the era where the slash occurred, if any. - fn compare_and_update_span_slash( - &mut self, - slash_era: EraIndex, - slash: BalanceOf, - ) -> Option { - let target_span = self.era_span(slash_era)?; - let span_slash_key = (self.stash.clone(), target_span.index); - let mut span_record = SpanSlash::::get(&span_slash_key); - let mut changed = false; - - let reward = if span_record.slashed < slash { - // new maximum span slash. apply the difference. - let difference = slash - span_record.slashed; - span_record.slashed = slash; - - // compute reward. - let reward = - REWARD_F1 * (self.reward_proportion * slash).saturating_sub(span_record.paid_out); - - self.add_slash(difference, slash_era); - changed = true; - - reward - } else if span_record.slashed == slash { - // compute reward. no slash difference to apply. - REWARD_F1 * (self.reward_proportion * slash).saturating_sub(span_record.paid_out) - } else { - Zero::zero() - }; - - if !reward.is_zero() { - changed = true; - span_record.paid_out += reward; - *self.paid_out += reward; - } - - if changed { - self.dirty = true; - SpanSlash::::insert(&span_slash_key, &span_record); - } - - Some(target_span.index) - } -} - -impl<'a, T: 'a + Config> Drop for InspectingSpans<'a, T> { - fn drop(&mut self) { - // only update on disk if we slashed this account. - if !self.dirty { - return - } - - if let Some((start, end)) = self.spans.prune(self.window_start) { - for span_index in start..end { - SpanSlash::::remove(&(self.stash.clone(), span_index)); - } - } - - crate::SlashingSpans::::insert(self.stash, &self.spans); - } -} - -/// Clear slashing metadata for an obsolete era. -pub(crate) fn clear_era_metadata(obsolete_era: EraIndex) { - #[allow(deprecated)] - ValidatorSlashInEra::::remove_prefix(&obsolete_era, None); - #[allow(deprecated)] - NominatorSlashInEra::::remove_prefix(&obsolete_era, None); -} - -/// Clear slashing metadata for a dead account. -pub(crate) fn clear_stash_metadata( - stash: &T::AccountId, - num_slashing_spans: u32, -) -> DispatchResult { - let spans = match crate::SlashingSpans::::get(stash) { - None => return Ok(()), - Some(s) => s, - }; - - ensure!( - num_slashing_spans as usize >= spans.iter().count(), - Error::::IncorrectSlashingSpans - ); - - crate::SlashingSpans::::remove(stash); - - // kill slashing-span metadata for account. - // - // this can only happen while the account is staked _if_ they are completely slashed. - // in that case, they may re-bond, but it would count again as span 0. Further ancient - // slashes would slash into this new bond, since metadata has now been cleared. - for span in spans.iter() { - SpanSlash::::remove(&(stash.clone(), span.index)); - } - - Ok(()) -} - -// apply the slash to a stash account, deducting any missing funds from the reward -// payout, saturating at 0. this is mildly unfair but also an edge-case that -// can only occur when overlapping locked funds have been slashed. -pub fn do_slash( - stash: &T::AccountId, - value: BalanceOf, - reward_payout: &mut BalanceOf, - slashed_imbalance: &mut NegativeImbalanceOf, - slash_era: EraIndex, -) { - let controller = match >::bonded(stash).defensive() { - None => return, - Some(c) => c, - }; - - let mut ledger = match >::ledger(&controller) { - Some(ledger) => ledger, - None => return, // nothing to do. - }; - - let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era); - - if !value.is_zero() { - let (imbalance, missing) = T::Currency::slash(stash, value); - slashed_imbalance.subsume(imbalance); - - if !missing.is_zero() { - // deduct overslash from the reward payout - *reward_payout = reward_payout.saturating_sub(missing); - } - - >::update_ledger(&controller, &ledger); - - // trigger the event - >::deposit_event(super::Event::::Slashed { - staker: stash.clone(), - amount: value, - }); - } -} - -/// Apply a previously-unapplied slash. -pub(crate) fn apply_slash( - unapplied_slash: UnappliedSlash>, - slash_era: EraIndex, -) { - let mut slashed_imbalance = NegativeImbalanceOf::::zero(); - let mut reward_payout = unapplied_slash.payout; - - do_slash::( - &unapplied_slash.validator, - unapplied_slash.own, - &mut reward_payout, - &mut slashed_imbalance, - slash_era, - ); - - for &(ref nominator, nominator_slash) in &unapplied_slash.others { - do_slash::( - nominator, - nominator_slash, - &mut reward_payout, - &mut slashed_imbalance, - slash_era, - ); - } - - pay_reporters::(reward_payout, slashed_imbalance, &unapplied_slash.reporters); -} - -/// Apply a reward payout to some reporters, paying the rewards out of the slashed imbalance. -fn pay_reporters( - reward_payout: BalanceOf, - slashed_imbalance: NegativeImbalanceOf, - reporters: &[T::AccountId], -) { - if reward_payout.is_zero() || reporters.is_empty() { - // nobody to pay out to or nothing to pay; - // just treat the whole value as slashed. - T::Slash::on_unbalanced(slashed_imbalance); - return - } - - // take rewards out of the slashed imbalance. - let reward_payout = reward_payout.min(slashed_imbalance.peek()); - let (mut reward_payout, mut value_slashed) = slashed_imbalance.split(reward_payout); - - let per_reporter = reward_payout.peek() / (reporters.len() as u32).into(); - for reporter in reporters { - let (reporter_reward, rest) = reward_payout.split(per_reporter); - reward_payout = rest; - - // this cancels out the reporter reward imbalance internally, leading - // to no change in total issuance. - T::Currency::resolve_creating(reporter, reporter_reward); - } - - // the rest goes to the on-slash imbalance handler (e.g. treasury) - value_slashed.subsume(reward_payout); // remainder of reward division remains. - T::Slash::on_unbalanced(value_slashed); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn span_contains_era() { - // unbounded end - let span = SlashingSpan { index: 0, start: 1000, length: None }; - assert!(!span.contains_era(0)); - assert!(!span.contains_era(999)); - - assert!(span.contains_era(1000)); - assert!(span.contains_era(1001)); - assert!(span.contains_era(10000)); - - // bounded end - non-inclusive range. - let span = SlashingSpan { index: 0, start: 1000, length: Some(10) }; - assert!(!span.contains_era(0)); - assert!(!span.contains_era(999)); - - assert!(span.contains_era(1000)); - assert!(span.contains_era(1001)); - assert!(span.contains_era(1009)); - assert!(!span.contains_era(1010)); - assert!(!span.contains_era(1011)); - } - - #[test] - fn single_slashing_span() { - let spans = SlashingSpans { - span_index: 0, - last_start: 1000, - last_nonzero_slash: 0, - prior: Vec::new(), - }; - - assert_eq!( - spans.iter().collect::>(), - vec![SlashingSpan { index: 0, start: 1000, length: None }], - ); - } - - #[test] - fn many_prior_spans() { - let spans = SlashingSpans { - span_index: 10, - last_start: 1000, - last_nonzero_slash: 0, - prior: vec![10, 9, 8, 10], - }; - - assert_eq!( - spans.iter().collect::>(), - vec![ - SlashingSpan { index: 10, start: 1000, length: None }, - SlashingSpan { index: 9, start: 990, length: Some(10) }, - SlashingSpan { index: 8, start: 981, length: Some(9) }, - SlashingSpan { index: 7, start: 973, length: Some(8) }, - SlashingSpan { index: 6, start: 963, length: Some(10) }, - ], - ) - } - - #[test] - fn pruning_spans() { - let mut spans = SlashingSpans { - span_index: 10, - last_start: 1000, - last_nonzero_slash: 0, - prior: vec![10, 9, 8, 10], - }; - - assert_eq!(spans.prune(981), Some((6, 8))); - assert_eq!( - spans.iter().collect::>(), - vec![ - SlashingSpan { index: 10, start: 1000, length: None }, - SlashingSpan { index: 9, start: 990, length: Some(10) }, - SlashingSpan { index: 8, start: 981, length: Some(9) }, - ], - ); - - assert_eq!(spans.prune(982), None); - assert_eq!( - spans.iter().collect::>(), - vec![ - SlashingSpan { index: 10, start: 1000, length: None }, - SlashingSpan { index: 9, start: 990, length: Some(10) }, - SlashingSpan { index: 8, start: 981, length: Some(9) }, - ], - ); - - assert_eq!(spans.prune(989), None); - assert_eq!( - spans.iter().collect::>(), - vec![ - SlashingSpan { index: 10, start: 1000, length: None }, - SlashingSpan { index: 9, start: 990, length: Some(10) }, - SlashingSpan { index: 8, start: 981, length: Some(9) }, - ], - ); - - assert_eq!(spans.prune(1000), Some((8, 10))); - assert_eq!( - spans.iter().collect::>(), - vec![SlashingSpan { index: 10, start: 1000, length: None },], - ); - - assert_eq!(spans.prune(2000), None); - assert_eq!( - spans.iter().collect::>(), - vec![SlashingSpan { index: 10, start: 2000, length: None },], - ); - - // now all in one shot. - let mut spans = SlashingSpans { - span_index: 10, - last_start: 1000, - last_nonzero_slash: 0, - prior: vec![10, 9, 8, 10], - }; - assert_eq!(spans.prune(2000), Some((6, 10))); - assert_eq!( - spans.iter().collect::>(), - vec![SlashingSpan { index: 10, start: 2000, length: None },], - ); - } - - #[test] - fn ending_span() { - let mut spans = SlashingSpans { - span_index: 1, - last_start: 10, - last_nonzero_slash: 0, - prior: Vec::new(), - }; - - assert!(spans.end_span(10)); - - assert_eq!( - spans.iter().collect::>(), - vec![ - SlashingSpan { index: 2, start: 11, length: None }, - SlashingSpan { index: 1, start: 10, length: Some(1) }, - ], - ); - - assert!(spans.end_span(15)); - assert_eq!( - spans.iter().collect::>(), - vec![ - SlashingSpan { index: 3, start: 16, length: None }, - SlashingSpan { index: 2, start: 11, length: Some(5) }, - SlashingSpan { index: 1, start: 10, length: Some(1) }, - ], - ); - - // does nothing if not a valid end. - assert!(!spans.end_span(15)); - assert_eq!( - spans.iter().collect::>(), - vec![ - SlashingSpan { index: 3, start: 16, length: None }, - SlashingSpan { index: 2, start: 11, length: Some(5) }, - SlashingSpan { index: 1, start: 10, length: Some(1) }, - ], - ); - } -} diff --git a/pallets/staking/src/testing_utils.rs b/pallets/staking/src/testing_utils.rs deleted file mode 100644 index 6182f66f0..000000000 --- a/pallets/staking/src/testing_utils.rs +++ /dev/null @@ -1,240 +0,0 @@ -// 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. - -//! Testing utils for staking. Provides some common functions to setup staking state, such as -//! bonding validators, nominators, and generating different types of solutions. - -use frame_benchmarking::account; -use frame_election_provider_support::SortedListProvider; -use frame_support::{pallet_prelude::*, traits::Currency}; -use frame_system::RawOrigin; -use rand_chacha::{ - rand_core::{RngCore, SeedableRng}, - ChaChaRng, -}; -use sp_io::hashing::blake2_256; -use sp_runtime::{traits::StaticLookup, Perbill}; -use sp_std::prelude::*; - -use crate::{Pallet as Staking, *}; - -const SEED: u32 = 0; - -/// This function removes all validators and nominators from storage. -pub fn clear_validators_and_nominators() { - #[allow(deprecated)] - Validators::::remove_all(); - - // whenever we touch nominators counter we should update `T::VoterList` as well. - #[allow(deprecated)] - Nominators::::remove_all(); - - // NOTE: safe to call outside block production - T::VoterList::unsafe_clear(); -} - -/// Grab a funded user. -pub fn create_funded_user( - string: &'static str, - n: u32, - balance_factor: u32, -) -> T::AccountId { - let user = account(string, n, SEED); - let balance = T::Currency::minimum_balance() * balance_factor.into(); - let _ = T::Currency::make_free_balance_be(&user, balance); - user -} - -/// Grab a funded user with max Balance. -pub fn create_funded_user_with_balance( - string: &'static str, - n: u32, - balance: BalanceOf, -) -> T::AccountId { - let user = account(string, n, SEED); - let _ = T::Currency::make_free_balance_be(&user, balance); - user -} - -/// Create a stash and controller pair. -pub fn create_stash_controller( - n: u32, - balance_factor: u32, - destination: RewardDestination, -) -> Result<(T::AccountId, T::AccountId), &'static str> { - let staker = create_funded_user::("stash", n, balance_factor); - let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); - Staking::::bond(RawOrigin::Signed(staker.clone()).into(), amount, destination)?; - Ok((staker.clone(), staker)) -} - -/// Create a unique stash and controller pair. -pub fn create_unique_stash_controller( - n: u32, - balance_factor: u32, - destination: RewardDestination, - dead_controller: bool, -) -> Result<(T::AccountId, T::AccountId), &'static str> { - let stash = create_funded_user::("stash", n, balance_factor); - - let controller = if dead_controller { - create_funded_user::("controller", n, 0) - } else { - create_funded_user::("controller", n, balance_factor) - }; - let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); - Staking::::bond(RawOrigin::Signed(stash.clone()).into(), amount, destination)?; - - // update ledger to be a *different* controller to stash - if let Some(l) = Ledger::::take(&stash) { - >::insert(&controller, l); - } - // update bonded account to be unique controller - >::insert(&stash, &controller); - - Ok((stash, controller)) -} - -/// Create a stash and controller pair with fixed balance. -pub fn create_stash_controller_with_balance( - n: u32, - balance: crate::BalanceOf, - destination: RewardDestination, -) -> Result<(T::AccountId, T::AccountId), &'static str> { - let staker = create_funded_user_with_balance::("stash", n, balance); - Staking::::bond(RawOrigin::Signed(staker.clone()).into(), balance, destination)?; - Ok((staker.clone(), staker)) -} - -/// Create a stash and controller pair, where payouts go to a dead payee account. This is used to -/// test worst case payout scenarios. -pub fn create_stash_and_dead_payee( - n: u32, - balance_factor: u32, -) -> Result<(T::AccountId, T::AccountId), &'static str> { - let staker = create_funded_user::("stash", n, 0); - // payee has no funds - let payee = create_funded_user::("payee", n, 0); - let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); - Staking::::bond( - RawOrigin::Signed(staker.clone()).into(), - amount, - RewardDestination::Account(payee), - )?; - Ok((staker.clone(), staker)) -} - -/// create `max` validators. -pub fn create_validators( - max: u32, - balance_factor: u32, -) -> Result>, &'static str> { - create_validators_with_seed::(max, balance_factor, 0) -} - -/// create `max` validators, with a seed to help unintentional prevent account collisions. -pub fn create_validators_with_seed( - max: u32, - balance_factor: u32, - seed: u32, -) -> Result>, &'static str> { - let mut validators: Vec> = Vec::with_capacity(max as usize); - for i in 0..max { - let (stash, controller) = - create_stash_controller::(i + seed, balance_factor, RewardDestination::Staked)?; - let validator_prefs = - ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; - Staking::::validate(RawOrigin::Signed(controller).into(), validator_prefs)?; - let stash_lookup = T::Lookup::unlookup(stash); - validators.push(stash_lookup); - } - Ok(validators) -} - -/// This function generates validators and nominators who are randomly nominating -/// `edge_per_nominator` random validators (until `to_nominate` if provided). -/// -/// NOTE: This function will remove any existing validators or nominators to ensure -/// we are working with a clean state. -/// -/// Parameters: -/// - `validators`: number of bonded validators -/// - `nominators`: number of bonded nominators. -/// - `edge_per_nominator`: number of edge (vote) per nominator. -/// - `randomize_stake`: whether to randomize the stakes. -/// - `to_nominate`: if `Some(n)`, only the first `n` bonded validator are voted upon. Else, all of -/// them are considered and `edge_per_nominator` random validators are voted for. -/// -/// Return the validators chosen to be nominated. -pub fn create_validators_with_nominators_for_era( - validators: u32, - nominators: u32, - edge_per_nominator: usize, - randomize_stake: bool, - to_nominate: Option, -) -> Result>, &'static str> { - clear_validators_and_nominators::(); - - let mut validators_stash: Vec> = Vec::with_capacity(validators as usize); - let mut rng = ChaChaRng::from_seed(SEED.using_encoded(blake2_256)); - - // Create validators - for i in 0..validators { - let balance_factor = if randomize_stake { rng.next_u32() % 255 + 10 } else { 100u32 }; - let (v_stash, v_controller) = - create_stash_controller::(i, balance_factor, RewardDestination::Staked)?; - let validator_prefs = - ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; - Staking::::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?; - let stash_lookup = T::Lookup::unlookup(v_stash.clone()); - validators_stash.push(stash_lookup.clone()); - } - - let to_nominate = to_nominate.unwrap_or(validators_stash.len() as u32) as usize; - let validator_chosen = validators_stash[0..to_nominate].to_vec(); - - // Create nominators - for j in 0..nominators { - let balance_factor = if randomize_stake { rng.next_u32() % 255 + 10 } else { 100u32 }; - let (_n_stash, n_controller) = - create_stash_controller::(u32::MAX - j, balance_factor, RewardDestination::Staked)?; - - // Have them randomly validate - let mut available_validators = validator_chosen.clone(); - let mut selected_validators: Vec> = - Vec::with_capacity(edge_per_nominator); - - for _ in 0..validators.min(edge_per_nominator as u32) { - let selected = rng.next_u32() as usize % available_validators.len(); - let validator = available_validators.remove(selected); - selected_validators.push(validator); - } - Staking::::nominate( - RawOrigin::Signed(n_controller.clone()).into(), - selected_validators, - )?; - } - - ValidatorCount::::put(validators); - - Ok(validator_chosen) -} - -/// get the current era. -pub fn current_era() -> EraIndex { - >::current_era().unwrap_or(0) -} diff --git a/pallets/staking/src/tests.rs b/pallets/staking/src/tests.rs deleted file mode 100644 index 6c1514376..000000000 --- a/pallets/staking/src/tests.rs +++ /dev/null @@ -1,5847 +0,0 @@ -// 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. - -//! Tests for the module. - -use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support}; -use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, bounded_vec, - dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, - pallet_prelude::*, - traits::{Currency, Get, ReservableCurrency}, -}; -use mock::*; -use pallet_balances::Error as BalancesError; -use sp_runtime::{ - assert_eq_error_rate, - traits::{BadOrigin, Dispatchable}, - Perbill, Percent, Rounding, TokenError, -}; -use sp_staking::{ - offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - SessionIndex, -}; -use sp_std::prelude::*; -use substrate_test_utils::assert_eq_uvec; - -use super::{ConfigOp, Event, *}; - -#[test] -fn set_staking_configs_works() { - ExtBuilder::default().build_and_execute(|| { - // setting works - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - ConfigOp::Set(1_500), - ConfigOp::Set(2_000), - ConfigOp::Set(10), - ConfigOp::Set(20), - ConfigOp::Set(Percent::from_percent(75)), - ConfigOp::Set(Zero::zero()) - )); - assert_eq!(MinNominatorBond::::get(), 1_500); - assert_eq!(MinValidatorBond::::get(), 2_000); - assert_eq!(MaxNominatorsCount::::get(), Some(10)); - assert_eq!(MaxValidatorsCount::::get(), Some(20)); - assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(75))); - assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); - - // noop does nothing - assert_storage_noop!(assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - ConfigOp::Noop, - ConfigOp::Noop, - ConfigOp::Noop, - ConfigOp::Noop, - ConfigOp::Noop, - ConfigOp::Noop - ))); - - // removing works - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove - )); - assert_eq!(MinNominatorBond::::get(), 0); - assert_eq!(MinValidatorBond::::get(), 0); - assert_eq!(MaxNominatorsCount::::get(), None); - assert_eq!(MaxValidatorsCount::::get(), None); - assert_eq!(ChillThreshold::::get(), None); - assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); - }); -} - -#[test] -fn force_unstake_works() { - ExtBuilder::default().build_and_execute(|| { - // Account 11 (also controller) is stashed and locked - assert_eq!(Staking::bonded(&11), Some(11)); - // Adds 2 slashing spans - add_slash(&11); - // Cant transfer - assert_noop!( - Balances::transfer_allow_death(RuntimeOrigin::signed(11), 1, 10), - TokenError::Frozen, - ); - // Force unstake requires root. - assert_noop!(Staking::force_unstake(RuntimeOrigin::signed(11), 11, 2), BadOrigin); - // Force unstake needs correct number of slashing spans (for weight calculation) - assert_noop!( - Staking::force_unstake(RuntimeOrigin::root(), 11, 0), - Error::::IncorrectSlashingSpans - ); - // We now force them to unstake - assert_ok!(Staking::force_unstake(RuntimeOrigin::root(), 11, 2)); - // No longer bonded. - assert_eq!(Staking::bonded(&11), None); - // Transfer works. - assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(11), 1, 10)); - }); -} - -#[test] -fn kill_stash_works() { - ExtBuilder::default().build_and_execute(|| { - // Account 11 (also controller) is stashed and locked - assert_eq!(Staking::bonded(&11), Some(11)); - // Adds 2 slashing spans - add_slash(&11); - // Only can kill a stash account - assert_noop!(Staking::kill_stash(&12, 0), Error::::NotStash); - // Respects slashing span count - assert_noop!(Staking::kill_stash(&11, 0), Error::::IncorrectSlashingSpans); - // Correct inputs, everything works - assert_ok!(Staking::kill_stash(&11, 2)); - // No longer bonded. - assert_eq!(Staking::bonded(&11), None); - }); -} - -#[test] -fn basic_setup_works() { - // Verifies initial conditions of mock - ExtBuilder::default().build_and_execute(|| { - // Account 11 is stashed and locked, and is the controller - assert_eq!(Staking::bonded(&11), Some(11)); - // Account 21 is stashed and locked and is the controller - assert_eq!(Staking::bonded(&21), Some(21)); - // Account 1 is not a stashed - assert_eq!(Staking::bonded(&1), None); - - // Account 11 controls its own stash, which is 100 * balance_factor units - assert_eq!( - Staking::ledger(&11).unwrap(), - StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - } - ); - // Account 21 controls its own stash, which is 200 * balance_factor units - assert_eq!( - Staking::ledger(&21), - Some(StakingLedger { - stash: 21, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - // Account 1 does not control any stash - assert_eq!(Staking::ledger(&1), None); - - // ValidatorPrefs are default - assert_eq_uvec!( - >::iter().collect::>(), - vec![ - (31, ValidatorPrefs::default()), - (21, ValidatorPrefs::default()), - (11, ValidatorPrefs::default()) - ] - ); - - assert_eq!( - Staking::ledger(101), - Some(StakingLedger { - stash: 101, - total: 500, - active: 500, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - - assert_eq!( - Staking::eras_stakers(active_era(), 11), - Exposure { - total: 1125, - own: 1000, - others: vec![IndividualExposure { who: 101, value: 125 }] - }, - ); - assert_eq!( - Staking::eras_stakers(active_era(), 21), - Exposure { - total: 1375, - own: 1000, - others: vec![IndividualExposure { who: 101, value: 375 }] - }, - ); - - // initial total stake = 1125 + 1375 - assert_eq!(Staking::eras_total_stake(active_era()), 2500); - - // The number of validators required. - assert_eq!(Staking::validator_count(), 2); - - // Initial Era and session - assert_eq!(active_era(), 0); - - // Account 10 has `balance_factor` free balance - assert_eq!(Balances::free_balance(10), 1); - assert_eq!(Balances::free_balance(10), 1); - - // New era is not being forced - assert_eq!(Staking::force_era(), Forcing::NotForcing); - }); -} - -#[test] -fn change_controller_works() { - ExtBuilder::default().build_and_execute(|| { - let (stash, controller) = testing_utils::create_unique_stash_controller::( - 0, - 100, - RewardDestination::Staked, - false, - ) - .unwrap(); - - // ensure `stash` and `controller` are bonded as stash controller pair. - assert_eq!(Staking::bonded(&stash), Some(controller)); - - // `controller` can control `stash` who is initially a validator. - assert_ok!(Staking::chill(RuntimeOrigin::signed(controller))); - - // sets controller back to `stash`. - assert_ok!(Staking::set_controller(RuntimeOrigin::signed(stash))); - assert_eq!(Staking::bonded(&stash), Some(stash)); - mock::start_active_era(1); - - // `controller` is no longer in control. `stash` is now controller. - assert_noop!( - Staking::validate(RuntimeOrigin::signed(controller), ValidatorPrefs::default()), - Error::::NotController, - ); - assert_ok!(Staking::validate(RuntimeOrigin::signed(stash), ValidatorPrefs::default())); - }) -} - -#[test] -fn change_controller_already_paired_once_stash() { - ExtBuilder::default().build_and_execute(|| { - // 10 and 11 are bonded as controller and stash respectively. - assert_eq!(Staking::bonded(&11), Some(11)); - - // 11 is initially a validator. - assert_ok!(Staking::chill(RuntimeOrigin::signed(11))); - - // Controller cannot change once matching with stash. - assert_noop!( - Staking::set_controller(RuntimeOrigin::signed(11)), - Error::::AlreadyPaired - ); - assert_eq!(Staking::bonded(&11), Some(11)); - mock::start_active_era(1); - - // 10 is no longer in control. - assert_noop!( - Staking::validate(RuntimeOrigin::signed(10), ValidatorPrefs::default()), - Error::::NotController, - ); - assert_ok!(Staking::validate(RuntimeOrigin::signed(11), ValidatorPrefs::default())); - }) -} - -#[test] -fn rewards_should_work() { - ExtBuilder::default().nominate(true).session_per_era(3).build_and_execute(|| { - let init_balance_11 = Balances::total_balance(&11); - let init_balance_21 = Balances::total_balance(&21); - let init_balance_101 = Balances::total_balance(&101); - - // Set payees - Payee::::insert(11, RewardDestination::Controller); - Payee::::insert(21, RewardDestination::Controller); - Payee::::insert(101, RewardDestination::Controller); - - Pallet::::reward_by_ids(vec![(11, 50)]); - Pallet::::reward_by_ids(vec![(11, 50)]); - // This is the second validator of the current elected set. - Pallet::::reward_by_ids(vec![(21, 50)]); - - // Compute total payout now for whole duration of the session. - let total_payout_0 = current_total_payout_for_duration(reward_time_per_era()); - let maximum_payout = maximum_payout_for_duration(reward_time_per_era()); - - start_session(1); - assert_eq_uvec!(Session::validators(), vec![11, 21]); - - assert_eq!(Balances::total_balance(&11), init_balance_11); - assert_eq!(Balances::total_balance(&21), init_balance_21); - assert_eq!(Balances::total_balance(&101), init_balance_101); - assert_eq!( - Staking::eras_reward_points(active_era()), - EraRewardPoints { - total: 50 * 3, - individual: vec![(11, 100), (21, 50)].into_iter().collect(), - } - ); - let part_for_11 = Perbill::from_rational::(1000, 1125); - let part_for_21 = Perbill::from_rational::(1000, 1375); - let part_for_101_from_11 = Perbill::from_rational::(125, 1125); - let part_for_101_from_21 = Perbill::from_rational::(375, 1375); - - start_session(2); - start_session(3); - - assert_eq!(active_era(), 1); - assert_eq!(mock::RewardRemainderUnbalanced::get(), maximum_payout - total_payout_0,); - assert_eq!( - *mock::staking_events().last().unwrap(), - Event::EraPaid { - era_index: 0, - validator_payout: total_payout_0, - remainder: maximum_payout - total_payout_0 - } - ); - mock::make_all_reward_payment(0); - - assert_eq_error_rate!( - Balances::total_balance(&11), - init_balance_11 + part_for_11 * total_payout_0 * 2 / 3, - 2, - ); - assert_eq_error_rate!( - Balances::total_balance(&21), - init_balance_21 + part_for_21 * total_payout_0 * 1 / 3, - 2, - ); - assert_eq_error_rate!( - Balances::total_balance(&101), - init_balance_101 + - part_for_101_from_11 * total_payout_0 * 2 / 3 + - part_for_101_from_21 * total_payout_0 * 1 / 3, - 2 - ); - - assert_eq_uvec!(Session::validators(), vec![11, 21]); - Pallet::::reward_by_ids(vec![(11, 1)]); - - // Compute total payout now for whole duration as other parameter won't change - let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); - - mock::start_active_era(2); - assert_eq!( - mock::RewardRemainderUnbalanced::get(), - maximum_payout * 2 - total_payout_0 - total_payout_1, - ); - assert_eq!( - *mock::staking_events().last().unwrap(), - Event::EraPaid { - era_index: 1, - validator_payout: total_payout_1, - remainder: maximum_payout - total_payout_1 - } - ); - mock::make_all_reward_payment(1); - - assert_eq_error_rate!( - Balances::total_balance(&11), - init_balance_11 + part_for_11 * (total_payout_0 * 2 / 3 + total_payout_1), - 2, - ); - assert_eq_error_rate!( - Balances::total_balance(&21), - init_balance_21 + part_for_21 * total_payout_0 * 1 / 3, - 2, - ); - assert_eq_error_rate!( - Balances::total_balance(&101), - init_balance_101 + - part_for_101_from_11 * (total_payout_0 * 2 / 3 + total_payout_1) + - part_for_101_from_21 * total_payout_0 * 1 / 3, - 2 - ); - }); -} - -#[test] -fn staking_should_work() { - ExtBuilder::default().nominate(false).build_and_execute(|| { - // remember + compare this along with the test. - assert_eq_uvec!(validator_controllers(), vec![21, 11]); - - // put some money in account that we'll use. - for i in 1..5 { - let _ = Balances::make_free_balance_be(&i, 2000); - } - - // --- Block 2: - start_session(2); - // add a new candidate for being a validator. account 3 controlled by 4. - assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 1500, RewardDestination::Controller)); - assert_ok!(Staking::validate(RuntimeOrigin::signed(3), ValidatorPrefs::default())); - assert_ok!(Session::set_keys( - RuntimeOrigin::signed(3), - SessionKeys { other: 4.into() }, - vec![] - )); - - // No effects will be seen so far. - assert_eq_uvec!(validator_controllers(), vec![21, 11]); - - // --- Block 3: - start_session(3); - - // No effects will be seen so far. Era has not been yet triggered. - assert_eq_uvec!(validator_controllers(), vec![21, 11]); - - // --- Block 4: the validators will now be queued. - start_session(4); - assert_eq!(active_era(), 1); - - // --- Block 5: the validators are still in queue. - start_session(5); - - // --- Block 6: the validators will now be changed. - start_session(6); - - assert_eq_uvec!(validator_controllers(), vec![21, 3]); - // --- Block 6: Unstake 4 as a validator, freeing up the balance stashed in 3 - // 4 will chill - Staking::chill(RuntimeOrigin::signed(3)).unwrap(); - - // --- Block 7: nothing. 3 is still there. - start_session(7); - assert_eq_uvec!(validator_controllers(), vec![21, 3]); - - // --- Block 8: - start_session(8); - - // --- Block 9: 4 will not be a validator. - start_session(9); - assert_eq_uvec!(validator_controllers(), vec![21, 11]); - - // Note: the stashed value of 4 is still lock - assert_eq!( - Staking::ledger(&3), - Some(StakingLedger { - stash: 3, - total: 1500, - active: 1500, - unlocking: Default::default(), - claimed_rewards: bounded_vec![0], - }) - ); - // e.g. it cannot reserve more than 500 that it has free from the total 2000 - assert_noop!(Balances::reserve(&3, 501), BalancesError::::LiquidityRestrictions); - assert_ok!(Balances::reserve(&3, 409)); - }); -} - -#[test] -fn blocking_and_kicking_works() { - ExtBuilder::default() - .minimum_validator_count(1) - .validator_count(4) - .nominate(true) - .build_and_execute(|| { - // block validator 10/11 - assert_ok!(Staking::validate( - RuntimeOrigin::signed(11), - ValidatorPrefs { blocked: true, ..Default::default() } - )); - // attempt to nominate from 100/101... - assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![11])); - // should have worked since we're already nominated them - assert_eq!(Nominators::::get(&101).unwrap().targets, vec![11]); - // kick the nominator - assert_ok!(Staking::kick(RuntimeOrigin::signed(11), vec![101])); - // should have been kicked now - assert!(Nominators::::get(&101).unwrap().targets.is_empty()); - // attempt to nominate from 100/101... - assert_noop!( - Staking::nominate(RuntimeOrigin::signed(101), vec![11]), - Error::::BadTarget - ); - }); -} - -#[test] -fn less_than_needed_candidates_works() { - ExtBuilder::default() - .minimum_validator_count(1) - .validator_count(4) - .nominate(false) - .build_and_execute(|| { - assert_eq!(Staking::validator_count(), 4); - assert_eq!(Staking::minimum_validator_count(), 1); - assert_eq_uvec!(validator_controllers(), vec![31, 21, 11]); - - mock::start_active_era(1); - - // Previous set is selected. NO election algorithm is even executed. - assert_eq_uvec!(validator_controllers(), vec![31, 21, 11]); - - // But the exposure is updated in a simple way. No external votes exists. - // This is purely self-vote. - assert!(ErasStakers::::iter_prefix_values(active_era()) - .all(|exposure| exposure.others.is_empty())); - }); -} - -#[test] -fn no_candidate_emergency_condition() { - ExtBuilder::default() - .minimum_validator_count(1) - .validator_count(15) - .set_status(41, StakerStatus::Validator) - .nominate(false) - .build_and_execute(|| { - // initial validators - assert_eq_uvec!(validator_controllers(), vec![11, 21, 31, 41]); - let prefs = ValidatorPrefs { commission: Perbill::one(), ..Default::default() }; - Validators::::insert(11, prefs.clone()); - - // set the minimum validator count. - MinimumValidatorCount::::put(11); - - // try to chill - let res = Staking::chill(RuntimeOrigin::signed(11)); - assert_ok!(res); - - let current_era = CurrentEra::::get(); - - // try trigger new era - mock::run_to_block(21); - assert_eq!(*staking_events().last().unwrap(), Event::StakingElectionFailed); - // No new era is created - assert_eq!(current_era, CurrentEra::::get()); - - // Go to far further session to see if validator have changed - mock::run_to_block(100); - - // Previous ones are elected. chill is not effective in active era (as era hasn't - // changed) - assert_eq_uvec!(validator_controllers(), vec![11, 21, 31, 41]); - // The chill is still pending. - assert!(!Validators::::contains_key(11)); - // No new era is created. - assert_eq!(current_era, CurrentEra::::get()); - }); -} - -#[test] -fn nominating_and_rewards_should_work() { - ExtBuilder::default() - .nominate(false) - .set_status(41, StakerStatus::Validator) - .set_status(11, StakerStatus::Idle) - .set_status(31, StakerStatus::Idle) - .build_and_execute(|| { - // initial validators. - assert_eq_uvec!(validator_controllers(), vec![41, 21]); - - // re-validate with 11 and 31. - assert_ok!(Staking::validate(RuntimeOrigin::signed(11), Default::default())); - assert_ok!(Staking::validate(RuntimeOrigin::signed(31), Default::default())); - - // Set payee to controller. - assert_ok!(Staking::set_payee( - RuntimeOrigin::signed(11), - RewardDestination::Controller - )); - assert_ok!(Staking::set_payee( - RuntimeOrigin::signed(21), - RewardDestination::Controller - )); - assert_ok!(Staking::set_payee( - RuntimeOrigin::signed(31), - RewardDestination::Controller - )); - assert_ok!(Staking::set_payee( - RuntimeOrigin::signed(41), - RewardDestination::Controller - )); - - // give the man some money - let initial_balance = 1000; - for i in [1, 3, 5, 11, 21].iter() { - let _ = Balances::make_free_balance_be(i, initial_balance); - } - - // bond two account pairs and state interest in nomination. - assert_ok!(Staking::bond( - RuntimeOrigin::signed(1), - 1000, - RewardDestination::Controller - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(1), vec![11, 21, 31])); - - assert_ok!(Staking::bond( - RuntimeOrigin::signed(3), - 1000, - RewardDestination::Controller - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![11, 21, 41])); - - // the total reward for era 0 - let total_payout_0 = current_total_payout_for_duration(reward_time_per_era()); - Pallet::::reward_by_ids(vec![(41, 1)]); - Pallet::::reward_by_ids(vec![(21, 1)]); - - mock::start_active_era(1); - - // 10 and 20 have more votes, they will be chosen. - assert_eq_uvec!(validator_controllers(), vec![21, 11]); - - // old validators must have already received some rewards. - let initial_balance_41 = Balances::total_balance(&41); - let mut initial_balance_21 = Balances::total_balance(&21); - mock::make_all_reward_payment(0); - assert_eq!(Balances::total_balance(&41), initial_balance_41 + total_payout_0 / 2); - assert_eq!(Balances::total_balance(&21), initial_balance_21 + total_payout_0 / 2); - initial_balance_21 = Balances::total_balance(&21); - - assert_eq!(ErasStakers::::iter_prefix_values(active_era()).count(), 2); - assert_eq!( - Staking::eras_stakers(active_era(), 11), - Exposure { - total: 1000 + 800, - own: 1000, - others: vec![ - IndividualExposure { who: 1, value: 400 }, - IndividualExposure { who: 3, value: 400 }, - ] - }, - ); - assert_eq!( - Staking::eras_stakers(active_era(), 21), - Exposure { - total: 1000 + 1200, - own: 1000, - others: vec![ - IndividualExposure { who: 1, value: 600 }, - IndividualExposure { who: 3, value: 600 }, - ] - }, - ); - - // the total reward for era 1 - let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); - Pallet::::reward_by_ids(vec![(21, 2)]); - Pallet::::reward_by_ids(vec![(11, 1)]); - - mock::start_active_era(2); - - // nothing else will happen, era ends and rewards are paid again, it is expected that - // nominators will also be paid. See below - - mock::make_all_reward_payment(1); - let payout_for_11 = total_payout_1 / 3; - let payout_for_21 = 2 * total_payout_1 / 3; - // Nominator 2: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 21]'s reward. ==> - // 2/9 + 3/11 - assert_eq_error_rate!( - Balances::total_balance(&1), - initial_balance + (2 * payout_for_11 / 9 + 3 * payout_for_21 / 11), - 2, - ); - // Nominator 3: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 21]'s reward. ==> - // 2/9 + 3/11 - assert_eq_error_rate!( - Balances::total_balance(&3), - initial_balance + (2 * payout_for_11 / 9 + 3 * payout_for_21 / 11), - 2, - ); - - // Validator 11: got 800 / 1800 external stake => 8/18 =? 4/9 => Validator's share = 5/9 - assert_eq_error_rate!( - Balances::total_balance(&11), - initial_balance + 5 * payout_for_11 / 9, - 2, - ); - // Validator 21: got 1200 / 2200 external stake => 12/22 =? 6/11 => Validator's share = - // 5/11 - assert_eq_error_rate!( - Balances::total_balance(&21), - initial_balance_21 + 5 * payout_for_21 / 11, - 2, - ); - }); -} - -#[test] -fn nominators_also_get_slashed_pro_rata() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - let slash_percent = Perbill::from_percent(5); - let initial_exposure = Staking::eras_stakers(active_era(), 11); - // 101 is a nominator for 11 - assert_eq!(initial_exposure.others.first().unwrap().who, 101); - - // staked values; - let nominator_stake = Staking::ledger(101).unwrap().active; - let nominator_balance = balances(&101).0; - let validator_stake = Staking::ledger(11).unwrap().active; - let validator_balance = balances(&11).0; - let exposed_stake = initial_exposure.total; - let exposed_validator = initial_exposure.own; - let exposed_nominator = initial_exposure.others.first().unwrap().value; - - // 11 goes offline - on_offence_now( - &[OffenceDetails { offender: (11, initial_exposure.clone()), reporters: vec![] }], - &[slash_percent], - ); - - // both stakes must have been decreased. - assert!(Staking::ledger(101).unwrap().active < nominator_stake); - assert!(Staking::ledger(11).unwrap().active < validator_stake); - - let slash_amount = slash_percent * exposed_stake; - let validator_share = - Perbill::from_rational(exposed_validator, exposed_stake) * slash_amount; - let nominator_share = - Perbill::from_rational(exposed_nominator, exposed_stake) * slash_amount; - - // both slash amounts need to be positive for the test to make sense. - assert!(validator_share > 0); - assert!(nominator_share > 0); - - // both stakes must have been decreased pro-rata. - assert_eq!(Staking::ledger(101).unwrap().active, nominator_stake - nominator_share); - assert_eq!(Staking::ledger(11).unwrap().active, validator_stake - validator_share); - assert_eq!( - balances(&101).0, // free balance - nominator_balance - nominator_share, - ); - assert_eq!( - balances(&11).0, // free balance - validator_balance - validator_share, - ); - // Because slashing happened. - assert!(is_disabled(11)); - }); -} - -#[test] -fn double_staking_should_fail() { - // should test (in the same order): - // * an account already bonded as stash cannot be be stashed again. - // * an account already bonded as stash cannot nominate. - // * an account already bonded as controller can nominate. - ExtBuilder::default().build_and_execute(|| { - let arbitrary_value = 5; - let (stash, controller) = testing_utils::create_unique_stash_controller::( - 0, - arbitrary_value, - RewardDestination::default(), - false, - ) - .unwrap(); - - // 4 = not used so far, stash => not allowed. - assert_noop!( - Staking::bond( - RuntimeOrigin::signed(stash), - arbitrary_value.into(), - RewardDestination::default() - ), - Error::::AlreadyBonded, - ); - // stash => attempting to nominate should fail. - assert_noop!( - Staking::nominate(RuntimeOrigin::signed(stash), vec![1]), - Error::::NotController - ); - // controller => nominating should work. - assert_ok!(Staking::nominate(RuntimeOrigin::signed(controller), vec![1])); - }); -} - -#[test] -fn double_controlling_attempt_should_fail() { - // should test (in the same order): - // * an account already bonded as controller CANNOT be reused as the controller of another - // account. - ExtBuilder::default().build_and_execute(|| { - let arbitrary_value = 5; - let (stash, _) = testing_utils::create_unique_stash_controller::( - 0, - arbitrary_value, - RewardDestination::default(), - false, - ) - .unwrap(); - - // Note that controller (same as stash) is reused => no-op. - assert_noop!( - Staking::bond( - RuntimeOrigin::signed(stash), - arbitrary_value.into(), - RewardDestination::default() - ), - Error::::AlreadyBonded, - ); - }); -} - -#[test] -fn session_and_eras_work_simple() { - ExtBuilder::default().period(1).build_and_execute(|| { - assert_eq!(active_era(), 0); - assert_eq!(current_era(), 0); - assert_eq!(Session::current_index(), 1); - assert_eq!(System::block_number(), 1); - - // Session 1: this is basically a noop. This has already been started. - start_session(1); - assert_eq!(Session::current_index(), 1); - assert_eq!(active_era(), 0); - assert_eq!(System::block_number(), 1); - - // Session 2: No change. - start_session(2); - assert_eq!(Session::current_index(), 2); - assert_eq!(active_era(), 0); - assert_eq!(System::block_number(), 2); - - // Session 3: Era increment. - start_session(3); - assert_eq!(Session::current_index(), 3); - assert_eq!(active_era(), 1); - assert_eq!(System::block_number(), 3); - - // Session 4: No change. - start_session(4); - assert_eq!(Session::current_index(), 4); - assert_eq!(active_era(), 1); - assert_eq!(System::block_number(), 4); - - // Session 5: No change. - start_session(5); - assert_eq!(Session::current_index(), 5); - assert_eq!(active_era(), 1); - assert_eq!(System::block_number(), 5); - - // Session 6: Era increment. - start_session(6); - assert_eq!(Session::current_index(), 6); - assert_eq!(active_era(), 2); - assert_eq!(System::block_number(), 6); - }); -} - -#[test] -fn session_and_eras_work_complex() { - ExtBuilder::default().period(5).build_and_execute(|| { - assert_eq!(active_era(), 0); - assert_eq!(Session::current_index(), 0); - assert_eq!(System::block_number(), 1); - - start_session(1); - assert_eq!(Session::current_index(), 1); - assert_eq!(active_era(), 0); - assert_eq!(System::block_number(), 5); - - start_session(2); - assert_eq!(Session::current_index(), 2); - assert_eq!(active_era(), 0); - assert_eq!(System::block_number(), 10); - - start_session(3); - assert_eq!(Session::current_index(), 3); - assert_eq!(active_era(), 1); - assert_eq!(System::block_number(), 15); - - start_session(4); - assert_eq!(Session::current_index(), 4); - assert_eq!(active_era(), 1); - assert_eq!(System::block_number(), 20); - - start_session(5); - assert_eq!(Session::current_index(), 5); - assert_eq!(active_era(), 1); - assert_eq!(System::block_number(), 25); - - start_session(6); - assert_eq!(Session::current_index(), 6); - assert_eq!(active_era(), 2); - assert_eq!(System::block_number(), 30); - }); -} - -#[test] -fn forcing_new_era_works() { - ExtBuilder::default().build_and_execute(|| { - // normal flow of session. - start_session(1); - assert_eq!(active_era(), 0); - - start_session(2); - assert_eq!(active_era(), 0); - - start_session(3); - assert_eq!(active_era(), 1); - - // no era change. - Staking::set_force_era(Forcing::ForceNone); - - start_session(4); - assert_eq!(active_era(), 1); - - start_session(5); - assert_eq!(active_era(), 1); - - start_session(6); - assert_eq!(active_era(), 1); - - start_session(7); - assert_eq!(active_era(), 1); - - // back to normal. - // this immediately starts a new session. - Staking::set_force_era(Forcing::NotForcing); - - start_session(8); - assert_eq!(active_era(), 1); - - start_session(9); - assert_eq!(active_era(), 2); - // forceful change - Staking::set_force_era(Forcing::ForceAlways); - - start_session(10); - assert_eq!(active_era(), 2); - - start_session(11); - assert_eq!(active_era(), 3); - - start_session(12); - assert_eq!(active_era(), 4); - - // just one forceful change - Staking::set_force_era(Forcing::ForceNew); - start_session(13); - assert_eq!(active_era(), 5); - assert_eq!(ForceEra::::get(), Forcing::NotForcing); - - start_session(14); - assert_eq!(active_era(), 6); - - start_session(15); - assert_eq!(active_era(), 6); - }); -} - -#[test] -fn cannot_transfer_staked_balance() { - // Tests that a stash account cannot transfer funds - ExtBuilder::default().nominate(false).build_and_execute(|| { - // Confirm account 11 is stashed - assert_eq!(Staking::bonded(&11), Some(11)); - // Confirm account 11 has some free balance - assert_eq!(Balances::free_balance(11), 1000); - // Confirm account 11 (via controller) is totally staked - assert_eq!(Staking::eras_stakers(active_era(), 11).total, 1000); - // Confirm account 11 cannot transfer as a result - assert_noop!( - Balances::transfer_allow_death(RuntimeOrigin::signed(11), 21, 1), - TokenError::Frozen, - ); - - // Give account 11 extra free balance - let _ = Balances::make_free_balance_be(&11, 10000); - // Confirm that account 11 can now transfer some balance - assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(11), 21, 1)); - }); -} - -#[test] -fn cannot_transfer_staked_balance_2() { - // Tests that a stash account cannot transfer funds - // Same test as above but with 20, and more accurate. - // 21 has 2000 free balance but 1000 at stake - ExtBuilder::default().nominate(false).build_and_execute(|| { - // Confirm account 21 is stashed - assert_eq!(Staking::bonded(&21), Some(21)); - // Confirm account 21 has some free balance - assert_eq!(Balances::free_balance(21), 2000); - // Confirm account 21 (via controller) is totally staked - assert_eq!(Staking::eras_stakers(active_era(), 21).total, 1000); - // Confirm account 21 can transfer at most 1000 - assert_noop!( - Balances::transfer_allow_death(RuntimeOrigin::signed(21), 21, 1001), - TokenError::Frozen, - ); - assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(21), 21, 1000)); - }); -} - -#[test] -fn cannot_reserve_staked_balance() { - // Checks that a bonded account cannot reserve balance from free balance - ExtBuilder::default().build_and_execute(|| { - // Confirm account 11 is stashed - assert_eq!(Staking::bonded(&11), Some(11)); - // Confirm account 11 has some free balance - assert_eq!(Balances::free_balance(11), 1000); - // Confirm account 11 (via controller 10) is totally staked - assert_eq!(Staking::eras_stakers(active_era(), 11).own, 1000); - // Confirm account 11 cannot reserve as a result - assert_noop!(Balances::reserve(&11, 1), BalancesError::::LiquidityRestrictions); - - // Give account 11 extra free balance - let _ = Balances::make_free_balance_be(&11, 10000); - // Confirm account 11 can now reserve balance - assert_ok!(Balances::reserve(&11, 1)); - }); -} - -#[test] -fn reward_destination_works() { - // Rewards go to the correct destination as determined in Payee - ExtBuilder::default().nominate(false).build_and_execute(|| { - // Check that account 11 is a validator - assert!(Session::validators().contains(&11)); - // Check the balance of the validator account - assert_eq!(Balances::free_balance(10), 1); - // Check the balance of the stash account - assert_eq!(Balances::free_balance(11), 1000); - // Check how much is at stake - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - - // Compute total payout now for whole duration as other parameter won't change - let total_payout_0 = current_total_payout_for_duration(reward_time_per_era()); - Pallet::::reward_by_ids(vec![(11, 1)]); - - mock::start_active_era(1); - mock::make_all_reward_payment(0); - - // Check that RewardDestination is Staked (default) - assert_eq!(Staking::payee(&11), RewardDestination::Staked); - // Check that reward went to the stash account of validator - assert_eq!(Balances::free_balance(11), 1000 + total_payout_0); - // Check that amount at stake increased accordingly - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000 + total_payout_0, - active: 1000 + total_payout_0, - unlocking: Default::default(), - claimed_rewards: bounded_vec![0], - }) - ); - - // Change RewardDestination to Stash - >::insert(&11, RewardDestination::Stash); - - // Compute total payout now for whole duration as other parameter won't change - let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); - Pallet::::reward_by_ids(vec![(11, 1)]); - - mock::start_active_era(2); - mock::make_all_reward_payment(1); - - // Check that RewardDestination is Stash - assert_eq!(Staking::payee(&11), RewardDestination::Stash); - // Check that reward went to the stash account - assert_eq!(Balances::free_balance(11), 1000 + total_payout_0 + total_payout_1); - // Check that amount at stake is NOT increased - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000 + total_payout_0, - active: 1000 + total_payout_0, - unlocking: Default::default(), - claimed_rewards: bounded_vec![0, 1], - }) - ); - - // Change RewardDestination to Controller - >::insert(&11, RewardDestination::Controller); - - // Check controller balance - assert_eq!(Balances::free_balance(11), 23150); - - // Compute total payout now for whole duration as other parameter won't change - let total_payout_2 = current_total_payout_for_duration(reward_time_per_era()); - Pallet::::reward_by_ids(vec![(11, 1)]); - - mock::start_active_era(3); - mock::make_all_reward_payment(2); - - // Check that RewardDestination is Controller - assert_eq!(Staking::payee(&11), RewardDestination::Controller); - // Check that reward went to the controller account - assert_eq!(Balances::free_balance(11), 23150 + total_payout_2); - // Check that amount at stake is NOT increased - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000 + total_payout_0, - active: 1000 + total_payout_0, - unlocking: Default::default(), - claimed_rewards: bounded_vec![0, 1, 2], - }) - ); - }); -} - -#[test] -fn validator_payment_prefs_work() { - // Test that validator preferences are correctly honored - // Note: unstake threshold is being directly tested in slashing tests. - // This test will focus on validator payment. - ExtBuilder::default().build_and_execute(|| { - let commission = Perbill::from_percent(40); - >::insert(&11, ValidatorPrefs { commission, ..Default::default() }); - - // Reward controller so staked ratio doesn't change. - >::insert(&11, RewardDestination::Controller); - >::insert(&101, RewardDestination::Controller); - - mock::start_active_era(1); - mock::make_all_reward_payment(0); - - let balance_era_1_11 = Balances::total_balance(&11); - let balance_era_1_101 = Balances::total_balance(&101); - - // Compute total payout now for whole duration as other parameter won't change - let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); - let exposure_1 = Staking::eras_stakers(active_era(), 11); - Pallet::::reward_by_ids(vec![(11, 1)]); - - mock::start_active_era(2); - mock::make_all_reward_payment(1); - - let taken_cut = commission * total_payout_1; - let shared_cut = total_payout_1 - taken_cut; - let reward_of_10 = shared_cut * exposure_1.own / exposure_1.total + taken_cut; - let reward_of_100 = shared_cut * exposure_1.others[0].value / exposure_1.total; - assert_eq_error_rate!(Balances::total_balance(&11), balance_era_1_11 + reward_of_10, 2); - assert_eq_error_rate!(Balances::total_balance(&101), balance_era_1_101 + reward_of_100, 2); - }); -} - -#[test] -fn bond_extra_works() { - // Tests that extra `free_balance` in the stash can be added to stake - // NOTE: this tests only verifies `StakingLedger` for correct updates - // See `bond_extra_and_withdraw_unbonded_works` for more details and updates on `Exposure`. - ExtBuilder::default().build_and_execute(|| { - // Check that account 10 is a validator - assert!(>::contains_key(11)); - // Check that account 10 is bonded to account 11 - assert_eq!(Staking::bonded(&11), Some(11)); - // Check how much is at stake - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - - // Give account 11 some large free balance greater than total - let _ = Balances::make_free_balance_be(&11, 1000000); - - // Call the bond_extra function from controller, add only 100 - assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 100)); - // There should be 100 more `total` and `active` in the ledger - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000 + 100, - active: 1000 + 100, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - - // Call the bond_extra function with a large number, should handle it - assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), Balance::max_value())); - // The full amount of the funds should now be in the total and active - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000000, - active: 1000000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - }); -} - -#[test] -fn bond_extra_and_withdraw_unbonded_works() { - // - // * Should test - // * Given an account being bonded [and chosen as a validator](not mandatory) - // * It can add extra funds to the bonded account. - // * it can unbond a portion of its funds from the stash account. - // * Once the unbonding period is done, it can actually take the funds out of the stash. - ExtBuilder::default().nominate(false).build_and_execute(|| { - // Set payee to controller. avoids confusion - assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Controller)); - - // Give account 11 some large free balance greater than total - let _ = Balances::make_free_balance_be(&11, 1000000); - - // Initial config should be correct - assert_eq!(active_era(), 0); - - // check the balance of a validator accounts. - assert_eq!(Balances::total_balance(&11), 1000000); - - // confirm that 10 is a normal validator and gets paid at the end of the era. - mock::start_active_era(1); - - // Initial state of 11 - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - assert_eq!( - Staking::eras_stakers(active_era(), 11), - Exposure { total: 1000, own: 1000, others: vec![] } - ); - - // deposit the extra 100 units - Staking::bond_extra(RuntimeOrigin::signed(11), 100).unwrap(); - - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000 + 100, - active: 1000 + 100, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - // Exposure is a snapshot! only updated after the next era update. - assert_ne!( - Staking::eras_stakers(active_era(), 11), - Exposure { total: 1000 + 100, own: 1000 + 100, others: vec![] } - ); - - // trigger next era. - mock::start_active_era(2); - assert_eq!(active_era(), 2); - - // ledger should be the same. - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000 + 100, - active: 1000 + 100, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - // Exposure is now updated. - assert_eq!( - Staking::eras_stakers(active_era(), 11), - Exposure { total: 1000 + 100, own: 1000 + 100, others: vec![] } - ); - - // Unbond almost all of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(11), 1000).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000 + 100, - active: 100, - unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], - claimed_rewards: bounded_vec![], - }), - ); - - // Attempting to free the balances now will fail. 2 eras need to pass. - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000 + 100, - active: 100, - unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], - claimed_rewards: bounded_vec![], - }), - ); - - // trigger next era. - mock::start_active_era(3); - - // nothing yet - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000 + 100, - active: 100, - unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], - claimed_rewards: bounded_vec![], - }), - ); - - // trigger next era. - mock::start_active_era(5); - - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); - // Now the value is free and the staking ledger is updated. - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 100, - active: 100, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }), - ); - }) -} - -#[test] -fn many_unbond_calls_should_work() { - ExtBuilder::default().build_and_execute(|| { - let mut current_era = 0; - // locked at era MaxUnlockingChunks - 1 until 3 - - let max_unlocking_chunks = <::MaxUnlockingChunks as Get>::get(); - - for i in 0..max_unlocking_chunks - 1 { - // There is only 1 chunk per era, so we need to be in a new era to create a chunk. - current_era = i as u32; - mock::start_active_era(current_era); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1)); - } - - current_era += 1; - mock::start_active_era(current_era); - - // This chunk is locked at `current_era` through `current_era + 2` (because - // `BondingDuration` == 3). - assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1)); - assert_eq!( - Staking::ledger(&11).map(|l| l.unlocking.len()).unwrap(), - <::MaxUnlockingChunks as Get>::get() as usize - ); - - // even though the number of unlocked chunks is the same as `MaxUnlockingChunks`, - // unbonding works as expected. - for i in current_era..(current_era + max_unlocking_chunks) - 1 { - // There is only 1 chunk per era, so we need to be in a new era to create a chunk. - current_era = i as u32; - mock::start_active_era(current_era); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1)); - } - - // only slots within last `BondingDuration` are filled. - assert_eq!( - Staking::ledger(&11).map(|l| l.unlocking.len()).unwrap(), - <::BondingDuration>::get() as usize - ); - }) -} - -#[test] -fn auto_withdraw_may_not_unlock_all_chunks() { - ExtBuilder::default().build_and_execute(|| { - // set `MaxUnlockingChunks` to a low number to test case when the unbonding period - // is larger than the number of unlocking chunks available, which may result on a - // `Error::NoMoreChunks`, even when the auto-withdraw tries to release locked chunks. - MaxUnlockingChunks::set(1); - - let mut current_era = 0; - - // fills the chunking slots for account - mock::start_active_era(current_era); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1)); - - current_era += 1; - mock::start_active_era(current_era); - - // unbonding will fail because i) there are no remaining chunks and ii) no filled chunks - // can be released because current chunk hasn't stay in the queue for at least - // `BondingDuration` - assert_noop!(Staking::unbond(RuntimeOrigin::signed(11), 1), Error::::NoMoreChunks); - - // fast-forward a few eras for unbond to be successful with implicit withdraw - current_era += 10; - mock::start_active_era(current_era); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1)); - }) -} - -#[test] -fn rebond_works() { - // - // * Should test - // * Given an account being bonded [and chosen as a validator](not mandatory) - // * it can unbond a portion of its funds from the stash account. - // * it can re-bond a portion of the funds scheduled to unlock. - ExtBuilder::default().nominate(false).build_and_execute(|| { - // Set payee to controller. avoids confusion - assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Controller)); - - // Give account 11 some large free balance greater than total - let _ = Balances::make_free_balance_be(&11, 1000000); - - // confirm that 10 is a normal validator and gets paid at the end of the era. - mock::start_active_era(1); - - // Initial state of 11 - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - - mock::start_active_era(2); - assert_eq!(active_era(), 2); - - // Try to rebond some funds. We get an error since no fund is unbonded. - assert_noop!(Staking::rebond(RuntimeOrigin::signed(11), 500), Error::::NoUnlockChunk); - - // Unbond almost all of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(11), 900).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 100, - unlocking: bounded_vec![UnlockChunk { value: 900, era: 2 + 3 }], - claimed_rewards: bounded_vec![], - }) - ); - - // Re-bond all the funds unbonded. - Staking::rebond(RuntimeOrigin::signed(11), 900).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - - // Unbond almost all of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(11), 900).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 100, - unlocking: bounded_vec![UnlockChunk { value: 900, era: 5 }], - claimed_rewards: bounded_vec![], - }) - ); - - // Re-bond part of the funds unbonded. - Staking::rebond(RuntimeOrigin::signed(11), 500).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 600, - unlocking: bounded_vec![UnlockChunk { value: 400, era: 5 }], - claimed_rewards: bounded_vec![], - }) - ); - - // Re-bond the remainder of the funds unbonded. - Staking::rebond(RuntimeOrigin::signed(11), 500).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - - // Unbond parts of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); - Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); - Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 100, - unlocking: bounded_vec![UnlockChunk { value: 900, era: 5 }], - claimed_rewards: bounded_vec![], - }) - ); - - // Re-bond part of the funds unbonded. - Staking::rebond(RuntimeOrigin::signed(11), 500).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 600, - unlocking: bounded_vec![UnlockChunk { value: 400, era: 5 }], - claimed_rewards: bounded_vec![], - }) - ); - }) -} - -#[test] -fn rebond_is_fifo() { - // Rebond should proceed by reversing the most recent bond operations. - ExtBuilder::default().nominate(false).build_and_execute(|| { - // Set payee to controller. avoids confusion - assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Controller)); - - // Give account 11 some large free balance greater than total - let _ = Balances::make_free_balance_be(&11, 1000000); - - // confirm that 10 is a normal validator and gets paid at the end of the era. - mock::start_active_era(1); - - // Initial state of 10 - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - - mock::start_active_era(2); - - // Unbond some of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(11), 400).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 600, - unlocking: bounded_vec![UnlockChunk { value: 400, era: 2 + 3 }], - claimed_rewards: bounded_vec![], - }) - ); - - mock::start_active_era(3); - - // Unbond more of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 300, - unlocking: bounded_vec![ - UnlockChunk { value: 400, era: 2 + 3 }, - UnlockChunk { value: 300, era: 3 + 3 }, - ], - claimed_rewards: bounded_vec![], - }) - ); - - mock::start_active_era(4); - - // Unbond yet more of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(11), 200).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 100, - unlocking: bounded_vec![ - UnlockChunk { value: 400, era: 2 + 3 }, - UnlockChunk { value: 300, era: 3 + 3 }, - UnlockChunk { value: 200, era: 4 + 3 }, - ], - claimed_rewards: bounded_vec![], - }) - ); - - // Re-bond half of the unbonding funds. - Staking::rebond(RuntimeOrigin::signed(11), 400).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 500, - unlocking: bounded_vec![ - UnlockChunk { value: 400, era: 2 + 3 }, - UnlockChunk { value: 100, era: 3 + 3 }, - ], - claimed_rewards: bounded_vec![], - }) - ); - }) -} - -#[test] -fn rebond_emits_right_value_in_event() { - // When a user calls rebond with more than can be rebonded, things succeed, - // and the rebond event emits the actual value rebonded. - ExtBuilder::default().nominate(false).build_and_execute(|| { - // Set payee to controller. avoids confusion - assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Controller)); - - // Give account 11 some large free balance greater than total - let _ = Balances::make_free_balance_be(&11, 1000000); - - // confirm that 10 is a normal validator and gets paid at the end of the era. - mock::start_active_era(1); - - // Unbond almost all of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(11), 900).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 100, - unlocking: bounded_vec![UnlockChunk { value: 900, era: 1 + 3 }], - claimed_rewards: bounded_vec![], - }) - ); - - // Re-bond less than the total - Staking::rebond(RuntimeOrigin::signed(11), 100).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 200, - unlocking: bounded_vec![UnlockChunk { value: 800, era: 1 + 3 }], - claimed_rewards: bounded_vec![], - }) - ); - // Event emitted should be correct - assert_eq!(*staking_events().last().unwrap(), Event::Bonded { stash: 11, amount: 100 }); - - // Re-bond way more than available - Staking::rebond(RuntimeOrigin::signed(11), 100_000).unwrap(); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - // Event emitted should be correct, only 800 - assert_eq!(*staking_events().last().unwrap(), Event::Bonded { stash: 11, amount: 800 }); - }); -} - -#[test] -fn reward_to_stake_works() { - ExtBuilder::default() - .nominate(false) - .set_status(31, StakerStatus::Idle) - .set_status(41, StakerStatus::Idle) - .set_stake(21, 2000) - .build_and_execute(|| { - assert_eq!(Staking::validator_count(), 2); - // Confirm account 10 and 20 are validators - assert!(>::contains_key(&11) && >::contains_key(&21)); - - assert_eq!(Staking::eras_stakers(active_era(), 11).total, 1000); - assert_eq!(Staking::eras_stakers(active_era(), 21).total, 2000); - - // Give the man some money. - let _ = Balances::make_free_balance_be(&10, 1000); - let _ = Balances::make_free_balance_be(&20, 1000); - - // Bypass logic and change current exposure - ErasStakers::::insert(0, 21, Exposure { total: 69, own: 69, others: vec![] }); - >::insert( - &20, - StakingLedger { - stash: 21, - total: 69, - active: 69, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }, - ); - - // Compute total payout now for whole duration as other parameter won't change - let total_payout_0 = current_total_payout_for_duration(reward_time_per_era()); - Pallet::::reward_by_ids(vec![(11, 1)]); - Pallet::::reward_by_ids(vec![(21, 1)]); - - // New era --> rewards are paid --> stakes are changed - mock::start_active_era(1); - mock::make_all_reward_payment(0); - - assert_eq!(Staking::eras_stakers(active_era(), 11).total, 1000); - assert_eq!(Staking::eras_stakers(active_era(), 21).total, 2000); - - let _11_balance = Balances::free_balance(&11); - let _21_balance = Balances::free_balance(&21); - - assert_eq!(_11_balance, 1000 + total_payout_0 / 2); - assert_eq!(_21_balance, 2000 + total_payout_0 / 2); - - // Trigger another new era as the info are frozen before the era start. - mock::start_active_era(2); - - // -- new infos - assert_eq!(Staking::eras_stakers(active_era(), 11).total, _11_balance); - assert_eq!(Staking::eras_stakers(active_era(), 21).total, _21_balance); - }); -} - -#[test] -fn reap_stash_works() { - ExtBuilder::default() - .existential_deposit(10) - .balance_factor(10) - .build_and_execute(|| { - // given - assert_eq!(Balances::free_balance(11), 10 * 1000); - assert_eq!(Staking::bonded(&11), Some(11)); - - assert!(>::contains_key(&11)); - assert!(>::contains_key(&11)); - assert!(>::contains_key(&11)); - assert!(>::contains_key(&11)); - - // stash is not reapable - assert_noop!( - Staking::reap_stash(RuntimeOrigin::signed(20), 11, 0), - Error::::FundedTarget - ); - - // no easy way to cause an account to go below ED, we tweak their staking ledger - // instead. - Ledger::::insert( - 11, - StakingLedger { - stash: 11, - total: 5, - active: 5, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }, - ); - - // reap-able - assert_ok!(Staking::reap_stash(RuntimeOrigin::signed(20), 11, 0)); - - // then - assert!(!>::contains_key(&11)); - assert!(!>::contains_key(&11)); - assert!(!>::contains_key(&11)); - assert!(!>::contains_key(&11)); - }); -} - -#[test] -fn switching_roles() { - // Test that it should be possible to switch between roles (nominator, validator, idle) with - // minimal overhead. - ExtBuilder::default().nominate(false).build_and_execute(|| { - // Reset reward destination - for i in &[11, 21] { - assert_ok!(Staking::set_payee( - RuntimeOrigin::signed(*i), - RewardDestination::Controller - )); - } - - assert_eq_uvec!(validator_controllers(), vec![21, 11]); - - // put some money in account that we'll use. - for i in 1..7 { - let _ = Balances::deposit_creating(&i, 5000); - } - - // add 2 nominators - assert_ok!(Staking::bond(RuntimeOrigin::signed(1), 2000, RewardDestination::Controller)); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(1), vec![11, 5])); - - assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 500, RewardDestination::Controller)); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![21, 1])); - - // add a new validator candidate - assert_ok!(Staking::bond(RuntimeOrigin::signed(5), 1000, RewardDestination::Controller)); - assert_ok!(Staking::validate(RuntimeOrigin::signed(5), ValidatorPrefs::default())); - assert_ok!(Session::set_keys( - RuntimeOrigin::signed(5), - SessionKeys { other: 6.into() }, - vec![] - )); - - mock::start_active_era(1); - - // with current nominators 11 and 5 have the most stake - assert_eq_uvec!(validator_controllers(), vec![5, 11]); - - // 2 decides to be a validator. Consequences: - assert_ok!(Staking::validate(RuntimeOrigin::signed(1), ValidatorPrefs::default())); - assert_ok!(Session::set_keys( - RuntimeOrigin::signed(1), - SessionKeys { other: 2.into() }, - vec![] - )); - // new stakes: - // 11: 1000 self vote - // 21: 1000 self vote + 250 vote - // 5 : 1000 self vote - // 1 : 2000 self vote + 250 vote. - // Winners: 21 and 1 - - mock::start_active_era(2); - - assert_eq_uvec!(validator_controllers(), vec![1, 21]); - }); -} - -#[test] -fn wrong_vote_is_moot() { - ExtBuilder::default() - .add_staker( - 61, - 61, - 500, - StakerStatus::Nominator(vec![ - 11, 21, // good votes - 1, 2, 15, 1000, 25, // crap votes. No effect. - ]), - ) - .build_and_execute(|| { - // the genesis validators already reflect the above vote, nonetheless start a new era. - mock::start_active_era(1); - - // new validators - assert_eq_uvec!(validator_controllers(), vec![21, 11]); - - // our new voter is taken into account - assert!(Staking::eras_stakers(active_era(), 11).others.iter().any(|i| i.who == 61)); - assert!(Staking::eras_stakers(active_era(), 21).others.iter().any(|i| i.who == 61)); - }); -} - -#[test] -fn bond_with_no_staked_value() { - // Behavior when someone bonds with no staked value. - // Particularly when they votes and the candidate is elected. - ExtBuilder::default() - .validator_count(3) - .existential_deposit(5) - .balance_factor(5) - .nominate(false) - .minimum_validator_count(1) - .build_and_execute(|| { - // Can't bond with 1 - assert_noop!( - Staking::bond(RuntimeOrigin::signed(1), 1, RewardDestination::Controller), - Error::::InsufficientBond, - ); - // bonded with absolute minimum value possible. - assert_ok!(Staking::bond(RuntimeOrigin::signed(1), 5, RewardDestination::Controller)); - assert_eq!(Balances::locks(&1)[0].amount, 5); - - // unbonding even 1 will cause all to be unbonded. - assert_ok!(Staking::unbond(RuntimeOrigin::signed(1), 1)); - assert_eq!( - Staking::ledger(1), - Some(StakingLedger { - stash: 1, - active: 0, - total: 5, - unlocking: bounded_vec![UnlockChunk { value: 5, era: 3 }], - claimed_rewards: bounded_vec![], - }) - ); - - mock::start_active_era(1); - mock::start_active_era(2); - - // not yet removed. - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); - assert!(Staking::ledger(1).is_some()); - assert_eq!(Balances::locks(&1)[0].amount, 5); - - mock::start_active_era(3); - - // poof. Account 1 is removed from the staking system. - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); - assert!(Staking::ledger(1).is_none()); - assert_eq!(Balances::locks(&1).len(), 0); - }); -} - -#[test] -fn bond_with_little_staked_value_bounded() { - ExtBuilder::default() - .validator_count(3) - .nominate(false) - .minimum_validator_count(1) - .build_and_execute(|| { - // setup - assert_ok!(Staking::chill(RuntimeOrigin::signed(31))); - assert_ok!(Staking::set_payee( - RuntimeOrigin::signed(11), - RewardDestination::Controller - )); - let init_balance_1 = Balances::free_balance(&1); - let init_balance_11 = Balances::free_balance(&11); - - // Stingy validator. - assert_ok!(Staking::bond(RuntimeOrigin::signed(1), 1, RewardDestination::Controller)); - assert_ok!(Staking::validate(RuntimeOrigin::signed(1), ValidatorPrefs::default())); - assert_ok!(Session::set_keys( - RuntimeOrigin::signed(1), - SessionKeys { other: 1.into() }, - vec![] - )); - - // 1 era worth of reward. BUT, we set the timestamp after on_initialize, so outdated by - // one block. - let total_payout_0 = current_total_payout_for_duration(reward_time_per_era()); - - reward_all_elected(); - mock::start_active_era(1); - mock::make_all_reward_payment(0); - - // 2 is elected. - assert_eq_uvec!(validator_controllers(), vec![21, 11, 1]); - assert_eq!(Staking::eras_stakers(active_era(), 2).total, 0); - - // Old ones are rewarded. - assert_eq_error_rate!( - Balances::free_balance(11), - init_balance_11 + total_payout_0 / 3, - 1 - ); - // no rewards paid to 2. This was initial election. - assert_eq!(Balances::free_balance(1), init_balance_1); - - // reward era 2 - let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); - reward_all_elected(); - mock::start_active_era(2); - mock::make_all_reward_payment(1); - - assert_eq_uvec!(validator_controllers(), vec![21, 11, 1]); - assert_eq!(Staking::eras_stakers(active_era(), 2).total, 0); - - // 2 is now rewarded. - assert_eq_error_rate!( - Balances::free_balance(1), - init_balance_1 + total_payout_1 / 3, - 1 - ); - assert_eq_error_rate!( - Balances::free_balance(&11), - init_balance_11 + total_payout_0 / 3 + total_payout_1 / 3, - 2, - ); - }); -} - -#[test] -fn bond_with_duplicate_vote_should_be_ignored_by_election_provider() { - ExtBuilder::default() - .validator_count(2) - .nominate(false) - .minimum_validator_count(1) - .set_stake(31, 1000) - .build_and_execute(|| { - // ensure all have equal stake. - assert_eq!( - >::iter() - .map(|(v, _)| (v, Staking::ledger(v).unwrap().total)) - .collect::>(), - vec![(31, 1000), (21, 1000), (11, 1000)], - ); - // no nominators shall exist. - assert!(>::iter().map(|(n, _)| n).collect::>().is_empty()); - - // give the man some money. - let initial_balance = 1000; - for i in [1, 2, 3, 4].iter() { - let _ = Balances::make_free_balance_be(i, initial_balance); - } - - assert_ok!(Staking::bond( - RuntimeOrigin::signed(1), - 1000, - RewardDestination::Controller - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(1), vec![11, 11, 11, 21, 31])); - - assert_ok!(Staking::bond( - RuntimeOrigin::signed(3), - 1000, - RewardDestination::Controller - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![21, 31])); - - // winners should be 21 and 31. Otherwise this election is taking duplicates into - // account. - let supports = ::ElectionProvider::elect().unwrap(); - assert_eq!( - supports, - vec![ - (21, Support { total: 1800, voters: vec![(21, 1000), (1, 400), (3, 400)] }), - (31, Support { total: 2200, voters: vec![(31, 1000), (1, 600), (3, 600)] }) - ], - ); - }); -} - -#[test] -fn bond_with_duplicate_vote_should_be_ignored_by_election_provider_elected() { - // same as above but ensures that even when the dupe is being elected, everything is sane. - ExtBuilder::default() - .validator_count(2) - .nominate(false) - .set_stake(31, 1000) - .minimum_validator_count(1) - .build_and_execute(|| { - // ensure all have equal stake. - assert_eq!( - >::iter() - .map(|(v, _)| (v, Staking::ledger(v).unwrap().total)) - .collect::>(), - vec![(31, 1000), (21, 1000), (11, 1000)], - ); - - // no nominators shall exist. - assert!(>::iter().collect::>().is_empty()); - - // give the man some money. - let initial_balance = 1000; - for i in [1, 2, 3, 4].iter() { - let _ = Balances::make_free_balance_be(i, initial_balance); - } - - assert_ok!(Staking::bond( - RuntimeOrigin::signed(1), - 1000, - RewardDestination::Controller - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(1), vec![11, 11, 11, 21])); - - assert_ok!(Staking::bond( - RuntimeOrigin::signed(3), - 1000, - RewardDestination::Controller - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![21])); - - // winners should be 21 and 11. - let supports = ::ElectionProvider::elect().unwrap(); - assert_eq!( - supports, - vec![ - (11, Support { total: 1500, voters: vec![(11, 1000), (1, 500)] }), - (21, Support { total: 2500, voters: vec![(21, 1000), (1, 500), (3, 1000)] }) - ], - ); - }); -} - -#[test] -fn new_era_elects_correct_number_of_validators() { - ExtBuilder::default().nominate(true).validator_count(1).build_and_execute(|| { - assert_eq!(Staking::validator_count(), 1); - assert_eq!(validator_controllers().len(), 1); - - Session::on_initialize(System::block_number()); - - assert_eq!(validator_controllers().len(), 1); - }) -} - -#[test] -fn phragmen_should_not_overflow() { - ExtBuilder::default().nominate(false).build_and_execute(|| { - // This is the maximum value that we can have as the outcome of CurrencyToVote. - type Votes = u64; - - let _ = Staking::chill(RuntimeOrigin::signed(10)); - let _ = Staking::chill(RuntimeOrigin::signed(20)); - - bond_validator(3, Votes::max_value() as Balance); - bond_validator(5, Votes::max_value() as Balance); - - bond_nominator(7, Votes::max_value() as Balance, vec![3, 5]); - bond_nominator(9, Votes::max_value() as Balance, vec![3, 5]); - - mock::start_active_era(1); - - assert_eq_uvec!(validator_controllers(), vec![3, 5]); - - // We can safely convert back to values within [u64, u128]. - assert!(Staking::eras_stakers(active_era(), 3).total > Votes::max_value() as Balance); - assert!(Staking::eras_stakers(active_era(), 5).total > Votes::max_value() as Balance); - }) -} - -#[test] -fn reward_validator_slashing_validator_does_not_overflow() { - ExtBuilder::default().build_and_execute(|| { - let stake = u64::MAX as Balance * 2; - let reward_slash = u64::MAX as Balance * 2; - - // Assert multiplication overflows in balance arithmetic. - assert!(stake.checked_mul(reward_slash).is_none()); - - // Set staker - let _ = Balances::make_free_balance_be(&11, stake); - - let exposure = Exposure:: { total: stake, own: stake, others: vec![] }; - let reward = EraRewardPoints:: { - total: 1, - individual: vec![(11, 1)].into_iter().collect(), - }; - - // Check reward - ErasRewardPoints::::insert(0, reward); - ErasStakers::::insert(0, 11, &exposure); - ErasStakersClipped::::insert(0, 11, exposure); - ErasValidatorReward::::insert(0, stake); - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 0)); - assert_eq!(Balances::total_balance(&11), stake * 2); - - // Set staker - let _ = Balances::make_free_balance_be(&11, stake); - let _ = Balances::make_free_balance_be(&2, stake); - - // only slashes out of bonded stake are applied. without this line, it is 0. - Staking::bond(RuntimeOrigin::signed(2), stake - 1, RewardDestination::default()).unwrap(); - // Override exposure of 11 - ErasStakers::::insert( - 0, - 11, - Exposure { - total: stake, - own: 1, - others: vec![IndividualExposure { who: 2, value: stake - 1 }], - }, - ); - - // Check slashing - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(100)], - ); - - assert_eq!(Balances::total_balance(&11), stake - 1); - assert_eq!(Balances::total_balance(&2), 1); - }) -} - -#[test] -fn reward_from_authorship_event_handler_works() { - ExtBuilder::default().build_and_execute(|| { - use pallet_authorship::EventHandler; - - assert_eq!(>::author(), Some(11)); - - Pallet::::note_author(11); - Pallet::::note_author(11); - - // Not mandatory but must be coherent with rewards - assert_eq_uvec!(Session::validators(), vec![11, 21]); - - // 21 is rewarded as an uncle producer - // 11 is rewarded as a block producer and uncle referencer and uncle producer - assert_eq!( - ErasRewardPoints::::get(active_era()), - EraRewardPoints { individual: vec![(11, 20 * 2)].into_iter().collect(), total: 40 }, - ); - }) -} - -#[test] -fn add_reward_points_fns_works() { - ExtBuilder::default().build_and_execute(|| { - // Not mandatory but must be coherent with rewards - assert_eq_uvec!(Session::validators(), vec![21, 11]); - - Pallet::::reward_by_ids(vec![(21, 1), (11, 1), (11, 1)]); - - Pallet::::reward_by_ids(vec![(21, 1), (11, 1), (11, 1)]); - - assert_eq!( - ErasRewardPoints::::get(active_era()), - EraRewardPoints { individual: vec![(11, 4), (21, 2)].into_iter().collect(), total: 6 }, - ); - }) -} - -#[test] -fn unbonded_balance_is_not_slashable() { - ExtBuilder::default().build_and_execute(|| { - // total amount staked is slashable. - assert_eq!(Staking::slashable_balance_of(&11), 1000); - - assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 800)); - - // only the active portion. - assert_eq!(Staking::slashable_balance_of(&11), 200); - }) -} - -#[test] -fn era_is_always_same_length() { - // This ensures that the sessions is always of the same length if there is no forcing no - // session changes. - ExtBuilder::default().build_and_execute(|| { - let session_per_era = >::get(); - - mock::start_active_era(1); - assert_eq!(Staking::eras_start_session_index(current_era()).unwrap(), session_per_era); - - mock::start_active_era(2); - assert_eq!( - Staking::eras_start_session_index(current_era()).unwrap(), - session_per_era * 2u32 - ); - - let session = Session::current_index(); - Staking::set_force_era(Forcing::ForceNew); - advance_session(); - advance_session(); - assert_eq!(current_era(), 3); - assert_eq!(Staking::eras_start_session_index(current_era()).unwrap(), session + 2); - - mock::start_active_era(4); - assert_eq!( - Staking::eras_start_session_index(current_era()).unwrap(), - session + 2u32 + session_per_era - ); - }); -} - -#[test] -fn offence_forces_new_era() { - ExtBuilder::default().build_and_execute(|| { - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(5)], - ); - - assert_eq!(Staking::force_era(), Forcing::ForceNew); - }); -} - -#[test] -fn offence_ensures_new_era_without_clobbering() { - ExtBuilder::default().build_and_execute(|| { - assert_ok!(Staking::force_new_era_always(RuntimeOrigin::root())); - assert_eq!(Staking::force_era(), Forcing::ForceAlways); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(5)], - ); - - assert_eq!(Staking::force_era(), Forcing::ForceAlways); - }); -} - -#[test] -fn offence_deselects_validator_even_when_slash_is_zero() { - ExtBuilder::default().build_and_execute(|| { - assert!(Session::validators().contains(&11)); - assert!(>::contains_key(11)); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(0)], - ); - - assert_eq!(Staking::force_era(), Forcing::ForceNew); - assert!(!>::contains_key(11)); - - mock::start_active_era(1); - - assert!(!Session::validators().contains(&11)); - assert!(!>::contains_key(11)); - }); -} - -#[test] -fn slashing_performed_according_exposure() { - // This test checks that slashing is performed according the exposure (or more precisely, - // historical exposure), not the current balance. - ExtBuilder::default().build_and_execute(|| { - assert_eq!(Staking::eras_stakers(active_era(), 11).own, 1000); - - // Handle an offence with a historical exposure. - on_offence_now( - &[OffenceDetails { - offender: (11, Exposure { total: 500, own: 500, others: vec![] }), - reporters: vec![], - }], - &[Perbill::from_percent(50)], - ); - - // The stash account should be slashed for 250 (50% of 500). - assert_eq!(Balances::free_balance(11), 1000 - 250); - }); -} - -#[test] -fn slash_in_old_span_does_not_deselect() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - - assert!(>::contains_key(11)); - assert!(Session::validators().contains(&11)); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(0)], - ); - - assert_eq!(Staking::force_era(), Forcing::ForceNew); - assert!(!>::contains_key(11)); - - mock::start_active_era(2); - - Staking::validate(RuntimeOrigin::signed(11), Default::default()).unwrap(); - assert_eq!(Staking::force_era(), Forcing::NotForcing); - assert!(>::contains_key(11)); - assert!(!Session::validators().contains(&11)); - - mock::start_active_era(3); - - // this staker is in a new slashing span now, having re-registered after - // their prior slash. - - on_offence_in_era( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(0)], - 1, - DisableStrategy::WhenSlashed, - ); - - // the validator doesn't get chilled again - assert!(Validators::::iter().any(|(stash, _)| stash == 11)); - - // but we are still forcing a new era - assert_eq!(Staking::force_era(), Forcing::ForceNew); - - on_offence_in_era( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - // NOTE: A 100% slash here would clean up the account, causing de-registration. - &[Perbill::from_percent(95)], - 1, - DisableStrategy::WhenSlashed, - ); - - // the validator doesn't get chilled again - assert!(Validators::::iter().any(|(stash, _)| stash == 11)); - - // but it's disabled - assert!(is_disabled(11)); - // and we are still forcing a new era - assert_eq!(Staking::force_era(), Forcing::ForceNew); - }); -} - -#[test] -fn reporters_receive_their_slice() { - // This test verifies that the reporters of the offence receive their slice from the slashed - // amount. - ExtBuilder::default().build_and_execute(|| { - // The reporters' reward is calculated from the total exposure. - let initial_balance = 1125; - - assert_eq!(Staking::eras_stakers(active_era(), 11).total, initial_balance); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![1, 2], - }], - &[Perbill::from_percent(50)], - ); - - // F1 * (reward_proportion * slash - 0) - // 50% * (10% * initial_balance / 2) - let reward = (initial_balance / 20) / 2; - let reward_each = reward / 2; // split into two pieces. - assert_eq!(Balances::free_balance(1), 10 + reward_each); - assert_eq!(Balances::free_balance(2), 20 + reward_each); - }); -} - -#[test] -fn subsequent_reports_in_same_span_pay_out_less() { - // This test verifies that the reporters of the offence receive their slice from the slashed - // amount, but less and less if they submit multiple reports in one span. - ExtBuilder::default().build_and_execute(|| { - // The reporters' reward is calculated from the total exposure. - let initial_balance = 1125; - - assert_eq!(Staking::eras_stakers(active_era(), 11).total, initial_balance); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![1], - }], - &[Perbill::from_percent(20)], - ); - - // F1 * (reward_proportion * slash - 0) - // 50% * (10% * initial_balance * 20%) - let reward = (initial_balance / 5) / 20; - assert_eq!(Balances::free_balance(1), 10 + reward); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![1], - }], - &[Perbill::from_percent(50)], - ); - - let prior_payout = reward; - - // F1 * (reward_proportion * slash - prior_payout) - // 50% * (10% * (initial_balance / 2) - prior_payout) - let reward = ((initial_balance / 20) - prior_payout) / 2; - assert_eq!(Balances::free_balance(1), 10 + prior_payout + reward); - }); -} - -#[test] -fn invulnerables_are_not_slashed() { - // For invulnerable validators no slashing is performed. - ExtBuilder::default().invulnerables(vec![11]).build_and_execute(|| { - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(21), 2000); - - let exposure = Staking::eras_stakers(active_era(), 21); - let initial_balance = Staking::slashable_balance_of(&21); - - let nominator_balances: Vec<_> = - exposure.others.iter().map(|o| Balances::free_balance(&o.who)).collect(); - - on_offence_now( - &[ - OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }, - OffenceDetails { - offender: (21, Staking::eras_stakers(active_era(), 21)), - reporters: vec![], - }, - ], - &[Perbill::from_percent(50), Perbill::from_percent(20)], - ); - - // The validator 11 hasn't been slashed, but 21 has been. - assert_eq!(Balances::free_balance(11), 1000); - // 2000 - (0.2 * initial_balance) - assert_eq!(Balances::free_balance(21), 2000 - (2 * initial_balance / 10)); - - // ensure that nominators were slashed as well. - for (initial_balance, other) in nominator_balances.into_iter().zip(exposure.others) { - assert_eq!( - Balances::free_balance(&other.who), - initial_balance - (2 * other.value / 10), - ); - } - }); -} - -#[test] -fn dont_slash_if_fraction_is_zero() { - // Don't slash if the fraction is zero. - ExtBuilder::default().build_and_execute(|| { - assert_eq!(Balances::free_balance(11), 1000); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(0)], - ); - - // The validator hasn't been slashed. The new era is not forced. - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Staking::force_era(), Forcing::ForceNew); - }); -} - -#[test] -fn only_slash_for_max_in_era() { - // multiple slashes within one era are only applied if it is more than any previous slash in the - // same era. - ExtBuilder::default().build_and_execute(|| { - assert_eq!(Balances::free_balance(11), 1000); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(50)], - ); - - // The validator has been slashed and has been force-chilled. - assert_eq!(Balances::free_balance(11), 500); - assert_eq!(Staking::force_era(), Forcing::ForceNew); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(25)], - ); - - // The validator has not been slashed additionally. - assert_eq!(Balances::free_balance(11), 500); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(60)], - ); - - // The validator got slashed 10% more. - assert_eq!(Balances::free_balance(11), 400); - }) -} - -#[test] -fn garbage_collection_after_slashing() { - // ensures that `SlashingSpans` and `SpanSlash` of an account is removed after reaping. - ExtBuilder::default() - .existential_deposit(2) - .balance_factor(2) - .build_and_execute(|| { - assert_eq!(Balances::free_balance(11), 2000); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - ); - - assert_eq!(Balances::free_balance(11), 2000 - 200); - assert!(SlashingSpans::::get(&11).is_some()); - assert_eq!(SpanSlash::::get(&(11, 0)).amount(), &200); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(100)], - ); - - // validator and nominator slash in era are garbage-collected by era change, - // so we don't test those here. - - assert_eq!(Balances::free_balance(11), 2); - assert_eq!(Balances::total_balance(&11), 2); - - let slashing_spans = SlashingSpans::::get(&11).unwrap(); - assert_eq!(slashing_spans.iter().count(), 2); - - // reap_stash respects num_slashing_spans so that weight is accurate - assert_noop!( - Staking::reap_stash(RuntimeOrigin::signed(20), 11, 0), - Error::::IncorrectSlashingSpans - ); - assert_ok!(Staking::reap_stash(RuntimeOrigin::signed(20), 11, 2)); - - assert!(SlashingSpans::::get(&11).is_none()); - assert_eq!(SpanSlash::::get(&(11, 0)).amount(), &0); - }) -} - -#[test] -fn garbage_collection_on_window_pruning() { - // ensures that `ValidatorSlashInEra` and `NominatorSlashInEra` are cleared after - // `BondingDuration`. - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - - assert_eq!(Balances::free_balance(11), 1000); - let now = active_era(); - - let exposure = Staking::eras_stakers(now, 11); - assert_eq!(Balances::free_balance(101), 2000); - let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; - - on_offence_now( - &[OffenceDetails { offender: (11, Staking::eras_stakers(now, 11)), reporters: vec![] }], - &[Perbill::from_percent(10)], - ); - - assert_eq!(Balances::free_balance(11), 900); - assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); - - assert!(ValidatorSlashInEra::::get(&now, &11).is_some()); - assert!(NominatorSlashInEra::::get(&now, &101).is_some()); - - // + 1 because we have to exit the bonding window. - for era in (0..(BondingDuration::get() + 1)).map(|offset| offset + now + 1) { - assert!(ValidatorSlashInEra::::get(&now, &11).is_some()); - assert!(NominatorSlashInEra::::get(&now, &101).is_some()); - - mock::start_active_era(era); - } - - assert!(ValidatorSlashInEra::::get(&now, &11).is_none()); - assert!(NominatorSlashInEra::::get(&now, &101).is_none()); - }) -} - -#[test] -fn slashing_nominators_by_span_max() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - mock::start_active_era(2); - mock::start_active_era(3); - - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(21), 2000); - assert_eq!(Balances::free_balance(101), 2000); - assert_eq!(Staking::slashable_balance_of(&21), 1000); - - let exposure_11 = Staking::eras_stakers(active_era(), 11); - let exposure_21 = Staking::eras_stakers(active_era(), 21); - let nominated_value_11 = exposure_11.others.iter().find(|o| o.who == 101).unwrap().value; - let nominated_value_21 = exposure_21.others.iter().find(|o| o.who == 101).unwrap().value; - - on_offence_in_era( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - 2, - DisableStrategy::WhenSlashed, - ); - - assert_eq!(Balances::free_balance(11), 900); - - let slash_1_amount = Perbill::from_percent(10) * nominated_value_11; - assert_eq!(Balances::free_balance(101), 2000 - slash_1_amount); - - let expected_spans = vec![ - slashing::SlashingSpan { index: 1, start: 4, length: None }, - slashing::SlashingSpan { index: 0, start: 0, length: Some(4) }, - ]; - - let get_span = |account| SlashingSpans::::get(&account).unwrap(); - - assert_eq!(get_span(11).iter().collect::>(), expected_spans); - - assert_eq!(get_span(101).iter().collect::>(), expected_spans); - - // second slash: higher era, higher value, same span. - on_offence_in_era( - &[OffenceDetails { - offender: (21, Staking::eras_stakers(active_era(), 21)), - reporters: vec![], - }], - &[Perbill::from_percent(30)], - 3, - DisableStrategy::WhenSlashed, - ); - - // 11 was not further slashed, but 21 and 101 were. - assert_eq!(Balances::free_balance(11), 900); - assert_eq!(Balances::free_balance(21), 1700); - - let slash_2_amount = Perbill::from_percent(30) * nominated_value_21; - assert!(slash_2_amount > slash_1_amount); - - // only the maximum slash in a single span is taken. - assert_eq!(Balances::free_balance(101), 2000 - slash_2_amount); - - // third slash: in same era and on same validator as first, higher - // in-era value, but lower slash value than slash 2. - on_offence_in_era( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(20)], - 2, - DisableStrategy::WhenSlashed, - ); - - // 11 was further slashed, but 21 and 101 were not. - assert_eq!(Balances::free_balance(11), 800); - assert_eq!(Balances::free_balance(21), 1700); - - let slash_3_amount = Perbill::from_percent(20) * nominated_value_21; - assert!(slash_3_amount < slash_2_amount); - assert!(slash_3_amount > slash_1_amount); - - // only the maximum slash in a single span is taken. - assert_eq!(Balances::free_balance(101), 2000 - slash_2_amount); - }); -} - -#[test] -fn slashes_are_summed_across_spans() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - mock::start_active_era(2); - mock::start_active_era(3); - - assert_eq!(Balances::free_balance(21), 2000); - assert_eq!(Staking::slashable_balance_of(&21), 1000); - - let get_span = |account| SlashingSpans::::get(&account).unwrap(); - - on_offence_now( - &[OffenceDetails { - offender: (21, Staking::eras_stakers(active_era(), 21)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - ); - - let expected_spans = vec![ - slashing::SlashingSpan { index: 1, start: 4, length: None }, - slashing::SlashingSpan { index: 0, start: 0, length: Some(4) }, - ]; - - assert_eq!(get_span(21).iter().collect::>(), expected_spans); - assert_eq!(Balances::free_balance(21), 1900); - - // 21 has been force-chilled. re-signal intent to validate. - Staking::validate(RuntimeOrigin::signed(21), Default::default()).unwrap(); - - mock::start_active_era(4); - - assert_eq!(Staking::slashable_balance_of(&21), 900); - - on_offence_now( - &[OffenceDetails { - offender: (21, Staking::eras_stakers(active_era(), 21)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - ); - - let expected_spans = vec![ - slashing::SlashingSpan { index: 2, start: 5, length: None }, - slashing::SlashingSpan { index: 1, start: 4, length: Some(1) }, - slashing::SlashingSpan { index: 0, start: 0, length: Some(4) }, - ]; - - assert_eq!(get_span(21).iter().collect::>(), expected_spans); - assert_eq!(Balances::free_balance(21), 1810); - }); -} - -#[test] -fn deferred_slashes_are_deferred() { - ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { - mock::start_active_era(1); - - assert_eq!(Balances::free_balance(11), 1000); - - let exposure = Staking::eras_stakers(active_era(), 11); - assert_eq!(Balances::free_balance(101), 2000); - let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; - - System::reset_events(); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - ); - - // nominations are not removed regardless of the deferring. - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - - mock::start_active_era(2); - - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - - mock::start_active_era(3); - - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - - // at the start of era 4, slashes from era 1 are processed, - // after being deferred for at least 2 full eras. - mock::start_active_era(4); - - assert_eq!(Balances::free_balance(11), 900); - assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); - - assert!(matches!( - staking_events_since_last_call().as_slice(), - &[ - Event::Chilled { stash: 11 }, - Event::ForceEra { mode: Forcing::ForceNew }, - Event::SlashReported { validator: 11, slash_era: 1, .. }, - Event::StakersElected, - Event::ForceEra { mode: Forcing::NotForcing }, - .., - Event::Slashed { staker: 11, amount: 100 }, - Event::Slashed { staker: 101, amount: 12 } - ] - )); - }) -} - -#[test] -fn retroactive_deferred_slashes_two_eras_before() { - ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { - assert_eq!(BondingDuration::get(), 3); - - mock::start_active_era(1); - let exposure_11_at_era1 = Staking::eras_stakers(active_era(), 11); - - mock::start_active_era(3); - - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - - System::reset_events(); - on_offence_in_era( - &[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }], - &[Perbill::from_percent(10)], - 1, // should be deferred for two full eras, and applied at the beginning of era 4. - DisableStrategy::Never, - ); - - mock::start_active_era(4); - - assert!(matches!( - staking_events_since_last_call().as_slice(), - &[ - Event::Chilled { stash: 11 }, - Event::ForceEra { mode: Forcing::ForceNew }, - Event::SlashReported { validator: 11, slash_era: 1, .. }, - .., - Event::Slashed { staker: 11, amount: 100 }, - Event::Slashed { staker: 101, amount: 12 } - ] - )); - }) -} - -#[test] -fn retroactive_deferred_slashes_one_before() { - ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { - assert_eq!(BondingDuration::get(), 3); - - mock::start_active_era(1); - let exposure_11_at_era1 = Staking::eras_stakers(active_era(), 11); - - // unbond at slash era. - mock::start_active_era(2); - assert_ok!(Staking::chill(RuntimeOrigin::signed(11))); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 100)); - - mock::start_active_era(3); - System::reset_events(); - on_offence_in_era( - &[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }], - &[Perbill::from_percent(10)], - 2, // should be deferred for two full eras, and applied at the beginning of era 5. - DisableStrategy::Never, - ); - - mock::start_active_era(4); - - assert_eq!(Staking::ledger(11).unwrap().total, 1000); - // slash happens after the next line. - - mock::start_active_era(5); - assert!(matches!( - staking_events_since_last_call().as_slice(), - &[ - Event::SlashReported { validator: 11, slash_era: 2, .. }, - .., - Event::Slashed { staker: 11, amount: 100 }, - Event::Slashed { staker: 101, amount: 12 } - ] - )); - - // their ledger has already been slashed. - assert_eq!(Staking::ledger(11).unwrap().total, 900); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1000)); - assert_eq!(Staking::ledger(11).unwrap().total, 900); - }) -} - -#[test] -fn staker_cannot_bail_deferred_slash() { - // as long as SlashDeferDuration is less than BondingDuration, this should not be possible. - ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { - mock::start_active_era(1); - - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - - let exposure = Staking::eras_stakers(active_era(), 11); - let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - ); - - // now we chill - assert_ok!(Staking::chill(RuntimeOrigin::signed(101))); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(101), 500)); - - assert_eq!(Staking::current_era().unwrap(), 1); - assert_eq!(active_era(), 1); - - assert_eq!( - Ledger::::get(101).unwrap(), - StakingLedger { - active: 0, - total: 500, - stash: 101, - claimed_rewards: bounded_vec![], - unlocking: bounded_vec![UnlockChunk { era: 4u32, value: 500 }], - } - ); - - // no slash yet. - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - - // no slash yet. - mock::start_active_era(2); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - assert_eq!(Staking::current_era().unwrap(), 2); - assert_eq!(active_era(), 2); - - // no slash yet. - mock::start_active_era(3); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - assert_eq!(Staking::current_era().unwrap(), 3); - assert_eq!(active_era(), 3); - - // and cannot yet unbond: - assert_storage_noop!(assert!( - Staking::withdraw_unbonded(RuntimeOrigin::signed(101), 0).is_ok() - )); - assert_eq!( - Ledger::::get(101).unwrap().unlocking.into_inner(), - vec![UnlockChunk { era: 4u32, value: 500 as Balance }], - ); - - // at the start of era 4, slashes from era 1 are processed, - // after being deferred for at least 2 full eras. - mock::start_active_era(4); - - assert_eq!(Balances::free_balance(11), 900); - assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); - - // and the leftover of the funds can now be unbonded. - }) -} - -#[test] -fn remove_deferred() { - ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { - mock::start_active_era(1); - - assert_eq!(Balances::free_balance(11), 1000); - - let exposure = Staking::eras_stakers(active_era(), 11); - assert_eq!(Balances::free_balance(101), 2000); - let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; - - // deferred to start of era 4. - on_offence_now( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], - &[Perbill::from_percent(10)], - ); - - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - - mock::start_active_era(2); - - // reported later, but deferred to start of era 4 as well. - System::reset_events(); - on_offence_in_era( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], - &[Perbill::from_percent(15)], - 1, - DisableStrategy::WhenSlashed, - ); - - // fails if empty - assert_noop!( - Staking::cancel_deferred_slash(RuntimeOrigin::root(), 1, vec![]), - Error::::EmptyTargets - ); - - // cancel one of them. - assert_ok!(Staking::cancel_deferred_slash(RuntimeOrigin::root(), 4, vec![0])); - - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - - mock::start_active_era(3); - - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - - // at the start of era 4, slashes from era 1 are processed, - // after being deferred for at least 2 full eras. - mock::start_active_era(4); - - // the first slash for 10% was cancelled, but the 15% one not. - assert!(matches!( - staking_events_since_last_call().as_slice(), - &[ - Event::SlashReported { validator: 11, slash_era: 1, .. }, - .., - Event::Slashed { staker: 11, amount: 50 }, - Event::Slashed { staker: 101, amount: 7 } - ] - )); - - let slash_10 = Perbill::from_percent(10); - let slash_15 = Perbill::from_percent(15); - let initial_slash = slash_10 * nominated_value; - - let total_slash = slash_15 * nominated_value; - let actual_slash = total_slash - initial_slash; - - // 5% slash (15 - 10) processed now. - assert_eq!(Balances::free_balance(11), 950); - assert_eq!(Balances::free_balance(101), 2000 - actual_slash); - }) -} - -#[test] -fn remove_multi_deferred() { - ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { - mock::start_active_era(1); - - assert_eq!(Balances::free_balance(11), 1000); - - let exposure = Staking::eras_stakers(active_era(), 11); - assert_eq!(Balances::free_balance(101), 2000); - - on_offence_now( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], - &[Perbill::from_percent(10)], - ); - - on_offence_now( - &[OffenceDetails { - offender: (21, Staking::eras_stakers(active_era(), 21)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - ); - - on_offence_now( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], - &[Perbill::from_percent(25)], - ); - - on_offence_now( - &[OffenceDetails { offender: (42, exposure.clone()), reporters: vec![] }], - &[Perbill::from_percent(25)], - ); - - on_offence_now( - &[OffenceDetails { offender: (69, exposure.clone()), reporters: vec![] }], - &[Perbill::from_percent(25)], - ); - - assert_eq!(UnappliedSlashes::::get(&4).len(), 5); - - // fails if list is not sorted - assert_noop!( - Staking::cancel_deferred_slash(RuntimeOrigin::root(), 1, vec![2, 0, 4]), - Error::::NotSortedAndUnique - ); - // fails if list is not unique - assert_noop!( - Staking::cancel_deferred_slash(RuntimeOrigin::root(), 1, vec![0, 2, 2]), - Error::::NotSortedAndUnique - ); - // fails if bad index - assert_noop!( - Staking::cancel_deferred_slash(RuntimeOrigin::root(), 1, vec![1, 2, 3, 4, 5]), - Error::::InvalidSlashIndex - ); - - assert_ok!(Staking::cancel_deferred_slash(RuntimeOrigin::root(), 4, vec![0, 2, 4])); - - let slashes = UnappliedSlashes::::get(&4); - assert_eq!(slashes.len(), 2); - assert_eq!(slashes[0].validator, 21); - assert_eq!(slashes[1].validator, 42); - }) -} - -#[test] -fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_validator() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - assert_eq_uvec!(Session::validators(), vec![11, 21]); - - // pre-slash balance - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - - // 100 has approval for 11 as of now - assert!(Staking::nominators(101).unwrap().targets.contains(&11)); - - // 11 and 21 both have the support of 100 - let exposure_11 = Staking::eras_stakers(active_era(), &11); - let exposure_21 = Staking::eras_stakers(active_era(), &21); - - assert_eq!(exposure_11.total, 1000 + 125); - assert_eq!(exposure_21.total, 1000 + 375); - - on_offence_now( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], - &[Perbill::from_percent(10)], - ); - - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, - Event::Chilled { stash: 11 }, - Event::ForceEra { mode: Forcing::ForceNew }, - Event::SlashReported { - validator: 11, - fraction: Perbill::from_percent(10), - slash_era: 1 - }, - Event::Slashed { staker: 11, amount: 100 }, - Event::Slashed { staker: 101, amount: 12 }, - ] - ); - - // post-slash balance - let nominator_slash_amount_11 = 125 / 10; - assert_eq!(Balances::free_balance(11), 900); - assert_eq!(Balances::free_balance(101), 2000 - nominator_slash_amount_11); - - // check that validator was chilled. - assert!(Validators::::iter().all(|(stash, _)| stash != 11)); - - // actually re-bond the slashed validator - assert_ok!(Staking::validate(RuntimeOrigin::signed(11), Default::default())); - - mock::start_active_era(2); - let exposure_11 = Staking::eras_stakers(active_era(), &11); - let exposure_21 = Staking::eras_stakers(active_era(), &21); - - // 11's own expo is reduced. sum of support from 11 is less (448), which is 500 - // 900 + 146 - assert!(matches!(exposure_11, Exposure { own: 900, total: 1046, .. })); - // 1000 + 342 - assert!(matches!(exposure_21, Exposure { own: 1000, total: 1342, .. })); - assert_eq!(500 - 146 - 342, nominator_slash_amount_11); - }); -} - -#[test] -fn non_slashable_offence_doesnt_disable_validator() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - assert_eq_uvec!(Session::validators(), vec![11, 21]); - - let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11); - let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21); - - // offence with no slash associated - on_offence_now( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], - &[Perbill::zero()], - ); - - // it does NOT affect the nominator. - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - - // offence that slashes 25% of the bond - on_offence_now( - &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], - &[Perbill::from_percent(25)], - ); - - // it DOES NOT affect the nominator. - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, - Event::Chilled { stash: 11 }, - Event::ForceEra { mode: Forcing::ForceNew }, - Event::SlashReported { - validator: 11, - fraction: Perbill::from_percent(0), - slash_era: 1 - }, - Event::Chilled { stash: 21 }, - Event::SlashReported { - validator: 21, - fraction: Perbill::from_percent(25), - slash_era: 1 - }, - Event::Slashed { staker: 21, amount: 250 }, - Event::Slashed { staker: 101, amount: 94 } - ] - ); - - // the offence for validator 10 wasn't slashable so it wasn't disabled - assert!(!is_disabled(11)); - // whereas validator 20 gets disabled - assert!(is_disabled(21)); - }); -} - -#[test] -fn slashing_independent_of_disabling_validator() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - assert_eq_uvec!(Session::validators(), vec![11, 21]); - - let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11); - let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21); - - let now = Staking::active_era().unwrap().index; - - // offence with no slash associated, BUT disabling - on_offence_in_era( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], - &[Perbill::zero()], - now, - DisableStrategy::Always, - ); - - // nomination remains untouched. - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - - // offence that slashes 25% of the bond, BUT not disabling - on_offence_in_era( - &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], - &[Perbill::from_percent(25)], - now, - DisableStrategy::Never, - ); - - // nomination remains untouched. - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, - Event::Chilled { stash: 11 }, - Event::ForceEra { mode: Forcing::ForceNew }, - Event::SlashReported { - validator: 11, - fraction: Perbill::from_percent(0), - slash_era: 1 - }, - Event::Chilled { stash: 21 }, - Event::SlashReported { - validator: 21, - fraction: Perbill::from_percent(25), - slash_era: 1 - }, - Event::Slashed { staker: 21, amount: 250 }, - Event::Slashed { staker: 101, amount: 94 } - ] - ); - - // the offence for validator 10 was explicitly disabled - assert!(is_disabled(11)); - // whereas validator 21 is explicitly not disabled - assert!(!is_disabled(21)); - }); -} - -#[test] -fn offence_threshold_triggers_new_era() { - ExtBuilder::default() - .validator_count(4) - .set_status(41, StakerStatus::Validator) - .build_and_execute(|| { - mock::start_active_era(1); - assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41]); - - assert_eq!( - ::OffendingValidatorsThreshold::get(), - Perbill::from_percent(75), - ); - - // we have 4 validators and an offending validator threshold of 75%, - // once the third validator commits an offence a new era should be forced - - let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11); - let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21); - let exposure_31 = Staking::eras_stakers(Staking::active_era().unwrap().index, &31); - - on_offence_now( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], - &[Perbill::zero()], - ); - - assert_eq!(ForceEra::::get(), Forcing::NotForcing); - - on_offence_now( - &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], - &[Perbill::zero()], - ); - - assert_eq!(ForceEra::::get(), Forcing::NotForcing); - - on_offence_now( - &[OffenceDetails { offender: (31, exposure_31.clone()), reporters: vec![] }], - &[Perbill::zero()], - ); - - assert_eq!(ForceEra::::get(), Forcing::ForceNew); - }); -} - -#[test] -fn disabled_validators_are_kept_disabled_for_whole_era() { - ExtBuilder::default() - .validator_count(4) - .set_status(41, StakerStatus::Validator) - .build_and_execute(|| { - mock::start_active_era(1); - assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41]); - assert_eq!(::SessionsPerEra::get(), 3); - - let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11); - let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21); - - on_offence_now( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], - &[Perbill::zero()], - ); - - on_offence_now( - &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], - &[Perbill::from_percent(25)], - ); - - // nominations are not updated. - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - - // validator 11 should not be disabled since the offence wasn't slashable - assert!(!is_disabled(11)); - // validator 21 gets disabled since it got slashed - assert!(is_disabled(21)); - - advance_session(); - - // disabled validators should carry-on through all sessions in the era - assert!(!is_disabled(11)); - assert!(is_disabled(21)); - - // validator 11 should now get disabled - on_offence_now( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], - &[Perbill::from_percent(25)], - ); - - // nominations are not updated. - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - - advance_session(); - - // and both are disabled in the last session of the era - assert!(is_disabled(11)); - assert!(is_disabled(21)); - - mock::start_active_era(2); - - // when a new era starts disabled validators get cleared - assert!(!is_disabled(11)); - assert!(!is_disabled(21)); - }); -} - -#[test] -fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() { - // should check that: - // * rewards get paid until history_depth for both validators and nominators - // * an invalid era to claim doesn't update last_reward - // * double claim of one era fails - ExtBuilder::default().nominate(true).build_and_execute(|| { - // Consumed weight for all payout_stakers dispatches that fail - let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); - - let init_balance_11 = Balances::total_balance(&11); - let init_balance_101 = Balances::total_balance(&101); - - let part_for_11 = Perbill::from_rational::(1000, 1125); - let part_for_101 = Perbill::from_rational::(125, 1125); - - // Check state - Payee::::insert(11, RewardDestination::Controller); - Payee::::insert(101, RewardDestination::Controller); - - Pallet::::reward_by_ids(vec![(11, 1)]); - // Compute total payout now for whole duration as other parameter won't change - let total_payout_0 = current_total_payout_for_duration(reward_time_per_era()); - - mock::start_active_era(1); - - Pallet::::reward_by_ids(vec![(11, 1)]); - // Change total issuance in order to modify total payout - let _ = Balances::deposit_creating(&999, 1_000_000_000); - // Compute total payout now for whole duration as other parameter won't change - let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); - assert!(total_payout_1 != total_payout_0); - - mock::start_active_era(2); - - Pallet::::reward_by_ids(vec![(11, 1)]); - // Change total issuance in order to modify total payout - let _ = Balances::deposit_creating(&999, 1_000_000_000); - // Compute total payout now for whole duration as other parameter won't change - let total_payout_2 = current_total_payout_for_duration(reward_time_per_era()); - assert!(total_payout_2 != total_payout_0); - assert!(total_payout_2 != total_payout_1); - - mock::start_active_era(HistoryDepth::get() + 1); - - let active_era = active_era(); - - // This is the latest planned era in staking, not the active era - let current_era = Staking::current_era().unwrap(); - - // Last kept is 1: - assert!(current_era - HistoryDepth::get() == 1); - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 0), - // Fail: Era out of history - Error::::InvalidEraToReward.with_weight(err_weight) - ); - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 1)); - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 2)); - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 2), - // Fail: Double claim - Error::::AlreadyClaimed.with_weight(err_weight) - ); - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, active_era), - // Fail: Era not finished yet - Error::::InvalidEraToReward.with_weight(err_weight) - ); - - // Era 0 can't be rewarded anymore and current era can't be rewarded yet - // only era 1 and 2 can be rewarded. - - assert_eq!( - Balances::total_balance(&11), - init_balance_11 + part_for_11 * (total_payout_1 + total_payout_2), - ); - assert_eq!( - Balances::total_balance(&101), - init_balance_101 + part_for_101 * (total_payout_1 + total_payout_2), - ); - }); -} - -#[test] -fn zero_slash_keeps_nominators() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - - assert_eq!(Balances::free_balance(11), 1000); - - let exposure = Staking::eras_stakers(active_era(), 11); - assert_eq!(Balances::free_balance(101), 2000); - - on_offence_now( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], - &[Perbill::from_percent(0)], - ); - - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - - // 11 is still removed.. - assert!(Validators::::iter().all(|(stash, _)| stash != 11)); - // but their nominations are kept. - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - }); -} - -#[test] -fn six_session_delay() { - ExtBuilder::default().initialize_first_session(false).build_and_execute(|| { - use pallet_session::SessionManager; - - let val_set = Session::validators(); - let init_session = Session::current_index(); - let init_active_era = active_era(); - - // pallet-session is delaying session by one, thus the next session to plan is +2. - assert_eq!(>::new_session(init_session + 2), None); - assert_eq!( - >::new_session(init_session + 3), - Some(val_set.clone()) - ); - assert_eq!(>::new_session(init_session + 4), None); - assert_eq!(>::new_session(init_session + 5), None); - assert_eq!( - >::new_session(init_session + 6), - Some(val_set.clone()) - ); - - >::end_session(init_session); - >::start_session(init_session + 1); - assert_eq!(active_era(), init_active_era); - - >::end_session(init_session + 1); - >::start_session(init_session + 2); - assert_eq!(active_era(), init_active_era); - - // Reward current era - Staking::reward_by_ids(vec![(11, 1)]); - - // New active era is triggered here. - >::end_session(init_session + 2); - >::start_session(init_session + 3); - assert_eq!(active_era(), init_active_era + 1); - - >::end_session(init_session + 3); - >::start_session(init_session + 4); - assert_eq!(active_era(), init_active_era + 1); - - >::end_session(init_session + 4); - >::start_session(init_session + 5); - assert_eq!(active_era(), init_active_era + 1); - - // Reward current era - Staking::reward_by_ids(vec![(21, 2)]); - - // New active era is triggered here. - >::end_session(init_session + 5); - >::start_session(init_session + 6); - assert_eq!(active_era(), init_active_era + 2); - - // That reward are correct - assert_eq!(Staking::eras_reward_points(init_active_era).total, 1); - assert_eq!(Staking::eras_reward_points(init_active_era + 1).total, 2); - }); -} - -#[test] -fn test_max_nominator_rewarded_per_validator_and_cant_steal_someone_else_reward() { - ExtBuilder::default().build_and_execute(|| { - for i in 0..=<::MaxNominatorRewardedPerValidator as Get<_>>::get() { - let stash = 10_000 + i as AccountId; - let balance = 10_000 + i as Balance; - Balances::make_free_balance_be(&stash, balance); - assert_ok!(Staking::bond( - RuntimeOrigin::signed(stash), - balance, - RewardDestination::Stash - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(stash), vec![11])); - } - mock::start_active_era(1); - - Pallet::::reward_by_ids(vec![(11, 1)]); - // compute and ensure the reward amount is greater than zero. - let _ = current_total_payout_for_duration(reward_time_per_era()); - - mock::start_active_era(2); - mock::make_all_reward_payment(1); - - // Assert only nominators from 1 to Max are rewarded - for i in 0..=<::MaxNominatorRewardedPerValidator as Get<_>>::get() { - let stash = 10_000 + i as AccountId; - let balance = 10_000 + i as Balance; - if stash == 10_000 { - assert!(Balances::free_balance(&stash) == balance); - } else { - assert!(Balances::free_balance(&stash) > balance); - } - } - }); -} - -#[test] -fn test_payout_stakers() { - // Test that payout_stakers work in general, including that only the top - // `T::MaxNominatorRewardedPerValidator` nominators are rewarded. - ExtBuilder::default().has_stakers(false).build_and_execute(|| { - let balance = 1000; - // Track the exposure of the validator and all nominators. - let mut total_exposure = balance; - // Track the exposure of the validator and the nominators that will get paid out. - let mut payout_exposure = balance; - // Create a validator: - bond_validator(11, balance); // Default(64) - assert_eq!(Validators::::count(), 1); - - // Create nominators, targeting stash of validators - for i in 0..100 { - let bond_amount = balance + i as Balance; - bond_nominator(1000 + i, bond_amount, vec![11]); - total_exposure += bond_amount; - if i >= 36 { - payout_exposure += bond_amount; - }; - } - let payout_exposure_part = Perbill::from_rational(payout_exposure, total_exposure); - - mock::start_active_era(1); - Staking::reward_by_ids(vec![(11, 1)]); - - // compute and ensure the reward amount is greater than zero. - let payout = current_total_payout_for_duration(reward_time_per_era()); - let actual_paid_out = payout_exposure_part * payout; - - mock::start_active_era(2); - - let pre_payout_total_issuance = Balances::total_issuance(); - RewardOnUnbalanceWasCalled::set(false); - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 1)); - assert_eq_error_rate!( - Balances::total_issuance(), - pre_payout_total_issuance + actual_paid_out, - 1 - ); - assert!(RewardOnUnbalanceWasCalled::get()); - - // Top 64 nominators of validator 11 automatically paid out, including the validator - // Validator payout goes to controller. - assert!(Balances::free_balance(&11) > balance); - for i in 36..100 { - assert!(Balances::free_balance(&(1000 + i)) > balance + i as Balance); - } - // The bottom 36 do not - for i in 0..36 { - assert_eq!(Balances::free_balance(&(1000 + i)), balance + i as Balance); - } - - // We track rewards in `claimed_rewards` vec - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![1] - }) - ); - - for i in 3..16 { - Staking::reward_by_ids(vec![(11, 1)]); - - // compute and ensure the reward amount is greater than zero. - let payout = current_total_payout_for_duration(reward_time_per_era()); - let actual_paid_out = payout_exposure_part * payout; - let pre_payout_total_issuance = Balances::total_issuance(); - - mock::start_active_era(i); - RewardOnUnbalanceWasCalled::set(false); - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, i - 1)); - assert_eq_error_rate!( - Balances::total_issuance(), - pre_payout_total_issuance + actual_paid_out, - 1 - ); - assert!(RewardOnUnbalanceWasCalled::get()); - } - - // We track rewards in `claimed_rewards` vec - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: (1..=14).collect::>().try_into().unwrap() - }) - ); - - let last_era = 99; - let history_depth = HistoryDepth::get(); - let expected_last_reward_era = last_era - 1; - let expected_start_reward_era = last_era - history_depth; - for i in 16..=last_era { - Staking::reward_by_ids(vec![(11, 1)]); - // compute and ensure the reward amount is greater than zero. - let _ = current_total_payout_for_duration(reward_time_per_era()); - mock::start_active_era(i); - } - - // We clean it up as history passes - assert_ok!(Staking::payout_stakers( - RuntimeOrigin::signed(1337), - 11, - expected_start_reward_era - )); - assert_ok!(Staking::payout_stakers( - RuntimeOrigin::signed(1337), - 11, - expected_last_reward_era - )); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![expected_start_reward_era, expected_last_reward_era] - }) - ); - - // Out of order claims works. - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 69)); - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 23)); - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 42)); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![ - expected_start_reward_era, - 23, - 42, - 69, - expected_last_reward_era - ] - }) - ); - }); -} - -#[test] -fn payout_stakers_handles_basic_errors() { - // Here we will test payouts handle all errors. - ExtBuilder::default().has_stakers(false).build_and_execute(|| { - // Consumed weight for all payout_stakers dispatches that fail - let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); - - // Same setup as the test above - let balance = 1000; - bond_validator(11, balance); // Default(64) - - // Create nominators, targeting stash - for i in 0..100 { - bond_nominator(1000 + i, balance + i as Balance, vec![11]); - } - - mock::start_active_era(1); - Staking::reward_by_ids(vec![(11, 1)]); - - // compute and ensure the reward amount is greater than zero. - let _ = current_total_payout_for_duration(reward_time_per_era()); - - mock::start_active_era(2); - - // Wrong Era, too big - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 2), - Error::::InvalidEraToReward.with_weight(err_weight) - ); - // Wrong Staker - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(1337), 10, 1), - Error::::NotStash.with_weight(err_weight) - ); - - let last_era = 99; - for i in 3..=last_era { - Staking::reward_by_ids(vec![(11, 1)]); - // compute and ensure the reward amount is greater than zero. - let _ = current_total_payout_for_duration(reward_time_per_era()); - mock::start_active_era(i); - } - - let history_depth = HistoryDepth::get(); - let expected_last_reward_era = last_era - 1; - let expected_start_reward_era = last_era - history_depth; - - // We are at era last_era=99. Given history_depth=80, we should be able - // to payout era starting from expected_start_reward_era=19 through - // expected_last_reward_era=98 (80 total eras), but not 18 or 99. - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, expected_start_reward_era - 1), - Error::::InvalidEraToReward.with_weight(err_weight) - ); - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, expected_last_reward_era + 1), - Error::::InvalidEraToReward.with_weight(err_weight) - ); - assert_ok!(Staking::payout_stakers( - RuntimeOrigin::signed(1337), - 11, - expected_start_reward_era - )); - assert_ok!(Staking::payout_stakers( - RuntimeOrigin::signed(1337), - 11, - expected_last_reward_era - )); - - // Can't claim again - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, expected_start_reward_era), - Error::::AlreadyClaimed.with_weight(err_weight) - ); - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, expected_last_reward_era), - Error::::AlreadyClaimed.with_weight(err_weight) - ); - }); -} - -#[test] -fn payout_stakers_handles_weight_refund() { - // Note: this test relies on the assumption that `payout_stakers_alive_staked` is solely used by - // `payout_stakers` to calculate the weight of each payout op. - ExtBuilder::default().has_stakers(false).build_and_execute(|| { - let max_nom_rewarded = - <::MaxNominatorRewardedPerValidator as Get<_>>::get(); - // Make sure the configured value is meaningful for our use. - assert!(max_nom_rewarded >= 4); - let half_max_nom_rewarded = max_nom_rewarded / 2; - // Sanity check our max and half max nominator quantities. - assert!(half_max_nom_rewarded > 0); - assert!(max_nom_rewarded > half_max_nom_rewarded); - - let max_nom_rewarded_weight = - ::WeightInfo::payout_stakers_alive_staked(max_nom_rewarded); - let half_max_nom_rewarded_weight = - ::WeightInfo::payout_stakers_alive_staked(half_max_nom_rewarded); - let zero_nom_payouts_weight = ::WeightInfo::payout_stakers_alive_staked(0); - assert!(zero_nom_payouts_weight.any_gt(Weight::zero())); - assert!(half_max_nom_rewarded_weight.any_gt(zero_nom_payouts_weight)); - assert!(max_nom_rewarded_weight.any_gt(half_max_nom_rewarded_weight)); - - let balance = 1000; - bond_validator(11, balance); - - // Era 1 - start_active_era(1); - - // Reward just the validator. - Staking::reward_by_ids(vec![(11, 1)]); - - // Add some `half_max_nom_rewarded` nominators who will start backing the validator in the - // next era. - for i in 0..half_max_nom_rewarded { - bond_nominator((1000 + i).into(), balance + i as Balance, vec![11]); - } - - // Era 2 - start_active_era(2); - - // Collect payouts when there are no nominators - let call = TestCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 1 }); - let info = call.get_dispatch_info(); - let result = call.dispatch(RuntimeOrigin::signed(20)); - assert_ok!(result); - assert_eq!(extract_actual_weight(&result, &info), zero_nom_payouts_weight); - - // The validator is not rewarded in this era; so there will be zero payouts to claim for - // this era. - - // Era 3 - start_active_era(3); - - // Collect payouts for an era where the validator did not receive any points. - let call = TestCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 2 }); - let info = call.get_dispatch_info(); - let result = call.dispatch(RuntimeOrigin::signed(20)); - assert_ok!(result); - assert_eq!(extract_actual_weight(&result, &info), zero_nom_payouts_weight); - - // Reward the validator and its nominators. - Staking::reward_by_ids(vec![(11, 1)]); - - // Era 4 - start_active_era(4); - - // Collect payouts when the validator has `half_max_nom_rewarded` nominators. - let call = TestCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 3 }); - let info = call.get_dispatch_info(); - let result = call.dispatch(RuntimeOrigin::signed(20)); - assert_ok!(result); - assert_eq!(extract_actual_weight(&result, &info), half_max_nom_rewarded_weight); - - // Add enough nominators so that we are at the limit. They will be active nominators - // in the next era. - for i in half_max_nom_rewarded..max_nom_rewarded { - bond_nominator((1000 + i).into(), balance + i as Balance, vec![11]); - } - - // Era 5 - start_active_era(5); - // We now have `max_nom_rewarded` nominators actively nominating our validator. - - // Reward the validator so we can collect for everyone in the next era. - Staking::reward_by_ids(vec![(11, 1)]); - - // Era 6 - start_active_era(6); - - // Collect payouts when the validator had `half_max_nom_rewarded` nominators. - let call = TestCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 5 }); - let info = call.get_dispatch_info(); - let result = call.dispatch(RuntimeOrigin::signed(20)); - assert_ok!(result); - assert_eq!(extract_actual_weight(&result, &info), max_nom_rewarded_weight); - - // Try and collect payouts for an era that has already been collected. - let call = TestCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 5 }); - let info = call.get_dispatch_info(); - let result = call.dispatch(RuntimeOrigin::signed(20)); - assert!(result.is_err()); - // When there is an error the consumed weight == weight when there are 0 nominator payouts. - assert_eq!(extract_actual_weight(&result, &info), zero_nom_payouts_weight); - }); -} - -#[test] -fn bond_during_era_correctly_populates_claimed_rewards() { - ExtBuilder::default().has_stakers(false).build_and_execute(|| { - // Era = None - bond_validator(9, 1000); - assert_eq!( - Staking::ledger(&9), - Some(StakingLedger { - stash: 9, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - mock::start_active_era(5); - bond_validator(11, 1000); - assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: (0..5).collect::>().try_into().unwrap(), - }) - ); - - // make sure only era upto history depth is stored - let current_era = 99; - let last_reward_era = 99 - HistoryDepth::get(); - mock::start_active_era(current_era); - bond_validator(13, 1000); - assert_eq!( - Staking::ledger(&13), - Some(StakingLedger { - stash: 13, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: (last_reward_era..current_era) - .collect::>() - .try_into() - .unwrap(), - }) - ); - }); -} - -#[test] -fn offences_weight_calculated_correctly() { - ExtBuilder::default().nominate(true).build_and_execute(|| { - // On offence with zero offenders: 4 Reads, 1 Write - let zero_offence_weight = - ::DbWeight::get().reads_writes(4, 1); - assert_eq!( - Staking::on_offence(&[], &[Perbill::from_percent(50)], 0, DisableStrategy::WhenSlashed), - zero_offence_weight - ); - - // On Offence with N offenders, Unapplied: 4 Reads, 1 Write + 4 Reads, 5 Writes - let n_offence_unapplied_weight = ::DbWeight::get() - .reads_writes(4, 1) + - ::DbWeight::get().reads_writes(4, 5); - - let offenders: Vec< - OffenceDetails< - ::AccountId, - pallet_session::historical::IdentificationTuple, - >, - > = (1..10) - .map(|i| OffenceDetails { - offender: (i, Staking::eras_stakers(active_era(), i)), - reporters: vec![], - }) - .collect(); - assert_eq!( - Staking::on_offence( - &offenders, - &[Perbill::from_percent(50)], - 0, - DisableStrategy::WhenSlashed - ), - n_offence_unapplied_weight - ); - - // On Offence with one offenders, Applied - let one_offender = [OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![1], - }]; - - let n = 1; // Number of offenders - let rw = 3 + 3 * n; // rw reads and writes - let one_offence_unapplied_weight = - ::DbWeight::get().reads_writes(4, 1) - + - ::DbWeight::get().reads_writes(rw, rw) - // One `slash_cost` - + ::DbWeight::get().reads_writes(6, 5) - // `slash_cost` * nominators (1) - + ::DbWeight::get().reads_writes(6, 5) - // `reward_cost` * reporters (1) - + ::DbWeight::get().reads_writes(2, 2) - ; - - assert_eq!( - Staking::on_offence( - &one_offender, - &[Perbill::from_percent(50)], - 0, - DisableStrategy::WhenSlashed{} - ), - one_offence_unapplied_weight - ); - }); -} - -#[test] -fn payout_creates_controller() { - ExtBuilder::default().has_stakers(false).build_and_execute(|| { - let balance = 1000; - // Create a validator: - bond_validator(11, balance); - - // create a stash/controller pair and nominate - let (stash, controller) = testing_utils::create_unique_stash_controller::( - 0, - 100, - RewardDestination::Controller, - false, - ) - .unwrap(); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(controller), vec![11])); - - // kill controller - assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(controller), stash, 100)); - assert_eq!(Balances::free_balance(controller), 0); - - mock::start_active_era(1); - Staking::reward_by_ids(vec![(11, 1)]); - // compute and ensure the reward amount is greater than zero. - let _ = current_total_payout_for_duration(reward_time_per_era()); - mock::start_active_era(2); - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(controller), 11, 1)); - - // Controller is created - assert!(Balances::free_balance(controller) > 0); - }) -} - -#[test] -fn payout_to_any_account_works() { - ExtBuilder::default().has_stakers(false).build_and_execute(|| { - let balance = 1000; - // Create a validator: - bond_validator(11, balance); // Default(64) - - // Create a stash/controller pair - bond_nominator(1234, 100, vec![11]); - - // Update payout location - assert_ok!(Staking::set_payee(RuntimeOrigin::signed(1234), RewardDestination::Account(42))); - - // Reward Destination account doesn't exist - assert_eq!(Balances::free_balance(42), 0); - - mock::start_active_era(1); - Staking::reward_by_ids(vec![(11, 1)]); - // compute and ensure the reward amount is greater than zero. - let _ = current_total_payout_for_duration(reward_time_per_era()); - mock::start_active_era(2); - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 1)); - - // Payment is successful - assert!(Balances::free_balance(42) > 0); - }) -} - -#[test] -fn session_buffering_with_offset() { - // similar to live-chains, have some offset for the first session - ExtBuilder::default() - .offset(2) - .period(5) - .session_per_era(5) - .build_and_execute(|| { - assert_eq!(current_era(), 0); - assert_eq!(active_era(), 0); - assert_eq!(Session::current_index(), 0); - - start_session(1); - assert_eq!(current_era(), 0); - assert_eq!(active_era(), 0); - assert_eq!(Session::current_index(), 1); - assert_eq!(System::block_number(), 2); - - start_session(2); - assert_eq!(current_era(), 0); - assert_eq!(active_era(), 0); - assert_eq!(Session::current_index(), 2); - assert_eq!(System::block_number(), 7); - - start_session(3); - assert_eq!(current_era(), 0); - assert_eq!(active_era(), 0); - assert_eq!(Session::current_index(), 3); - assert_eq!(System::block_number(), 12); - - // active era is lagging behind by one session, because of how session module works. - start_session(4); - assert_eq!(current_era(), 1); - assert_eq!(active_era(), 0); - assert_eq!(Session::current_index(), 4); - assert_eq!(System::block_number(), 17); - - start_session(5); - assert_eq!(current_era(), 1); - assert_eq!(active_era(), 1); - assert_eq!(Session::current_index(), 5); - assert_eq!(System::block_number(), 22); - - // go all the way to active 2. - start_active_era(2); - assert_eq!(current_era(), 2); - assert_eq!(active_era(), 2); - assert_eq!(Session::current_index(), 10); - }); -} - -#[test] -fn session_buffering_no_offset() { - // no offset, first session starts immediately - ExtBuilder::default() - .offset(0) - .period(5) - .session_per_era(5) - .build_and_execute(|| { - assert_eq!(current_era(), 0); - assert_eq!(active_era(), 0); - assert_eq!(Session::current_index(), 0); - - start_session(1); - assert_eq!(current_era(), 0); - assert_eq!(active_era(), 0); - assert_eq!(Session::current_index(), 1); - assert_eq!(System::block_number(), 5); - - start_session(2); - assert_eq!(current_era(), 0); - assert_eq!(active_era(), 0); - assert_eq!(Session::current_index(), 2); - assert_eq!(System::block_number(), 10); - - start_session(3); - assert_eq!(current_era(), 0); - assert_eq!(active_era(), 0); - assert_eq!(Session::current_index(), 3); - assert_eq!(System::block_number(), 15); - - // active era is lagging behind by one session, because of how session module works. - start_session(4); - assert_eq!(current_era(), 1); - assert_eq!(active_era(), 0); - assert_eq!(Session::current_index(), 4); - assert_eq!(System::block_number(), 20); - - start_session(5); - assert_eq!(current_era(), 1); - assert_eq!(active_era(), 1); - assert_eq!(Session::current_index(), 5); - assert_eq!(System::block_number(), 25); - - // go all the way to active 2. - start_active_era(2); - assert_eq!(current_era(), 2); - assert_eq!(active_era(), 2); - assert_eq!(Session::current_index(), 10); - }); -} - -#[test] -fn cannot_rebond_to_lower_than_ed() { - ExtBuilder::default() - .existential_deposit(11) - .balance_factor(11) - .build_and_execute(|| { - // initial stuff. - assert_eq!( - Staking::ledger(&21).unwrap(), - StakingLedger { - stash: 21, - total: 11 * 1000, - active: 11 * 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - } - ); - - // unbond all of it. must be chilled first. - assert_ok!(Staking::chill(RuntimeOrigin::signed(21))); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(21), 11 * 1000)); - assert_eq!( - Staking::ledger(&21).unwrap(), - StakingLedger { - stash: 21, - total: 11 * 1000, - active: 0, - unlocking: bounded_vec![UnlockChunk { value: 11 * 1000, era: 3 }], - claimed_rewards: bounded_vec![], - } - ); - - // now bond a wee bit more - assert_noop!( - Staking::rebond(RuntimeOrigin::signed(21), 5), - Error::::InsufficientBond - ); - }) -} - -#[test] -fn cannot_bond_extra_to_lower_than_ed() { - ExtBuilder::default() - .existential_deposit(11) - .balance_factor(11) - .build_and_execute(|| { - // initial stuff. - assert_eq!( - Staking::ledger(&21).unwrap(), - StakingLedger { - stash: 21, - total: 11 * 1000, - active: 11 * 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - } - ); - - // unbond all of it. must be chilled first. - assert_ok!(Staking::chill(RuntimeOrigin::signed(21))); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(21), 11 * 1000)); - assert_eq!( - Staking::ledger(&21).unwrap(), - StakingLedger { - stash: 21, - total: 11 * 1000, - active: 0, - unlocking: bounded_vec![UnlockChunk { value: 11 * 1000, era: 3 }], - claimed_rewards: bounded_vec![], - } - ); - - // now bond a wee bit more - assert_noop!( - Staking::bond_extra(RuntimeOrigin::signed(21), 5), - Error::::InsufficientBond, - ); - }) -} - -#[test] -fn do_not_die_when_active_is_ed() { - let ed = 10; - ExtBuilder::default() - .existential_deposit(ed) - .balance_factor(ed) - .build_and_execute(|| { - // given - assert_eq!( - Staking::ledger(&21).unwrap(), - StakingLedger { - stash: 21, - total: 1000 * ed, - active: 1000 * ed, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - } - ); - - // when unbond all of it except ed. - assert_ok!(Staking::unbond(RuntimeOrigin::signed(21), 999 * ed)); - start_active_era(3); - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(21), 100)); - - // then - assert_eq!( - Staking::ledger(&21).unwrap(), - StakingLedger { - stash: 21, - total: ed, - active: ed, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - } - ); - }) -} - -#[test] -fn on_finalize_weight_is_nonzero() { - ExtBuilder::default().build_and_execute(|| { - let on_finalize_weight = ::DbWeight::get().reads(1); - assert!(>::on_initialize(1).all_gte(on_finalize_weight)); - }) -} - -mod election_data_provider { - use frame_election_provider_support::ElectionDataProvider; - - use super::*; - - #[test] - fn targets_2sec_block() { - let mut validators = 1000; - while ::WeightInfo::get_npos_targets(validators).all_lt(Weight::from_parts( - 2u64 * frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, - u64::MAX, - )) { - validators += 1; - } - - println!("Can create a snapshot of {} validators in 2sec block", validators); - } - - #[test] - fn voters_2sec_block() { - // we assume a network only wants up to 1000 validators in most cases, thus having 2000 - // candidates is as high as it gets. - let validators = 2000; - let mut nominators = 1000; - - while ::WeightInfo::get_npos_voters(validators, nominators).all_lt( - Weight::from_parts( - 2u64 * frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, - u64::MAX, - ), - ) { - nominators += 1; - } - - println!( - "Can create a snapshot of {} nominators [{} validators, each 1 slashing] in 2sec block", - nominators, validators - ); - } - - #[test] - fn set_minimum_active_stake_is_correct() { - ExtBuilder::default() - .nominate(false) - .add_staker(61, 61, 2_000, StakerStatus::::Nominator(vec![21])) - .add_staker(71, 71, 10, StakerStatus::::Nominator(vec![21])) - .add_staker(81, 81, 50, StakerStatus::::Nominator(vec![21])) - .build_and_execute(|| { - assert_ok!(::electing_voters(None)); - assert_eq!(MinimumActiveStake::::get(), 10); - - // remove staker with lower bond by limiting the number of voters and check - // `MinimumActiveStake` again after electing voters. - assert_ok!(::electing_voters(Some(5))); - assert_eq!(MinimumActiveStake::::get(), 50); - }); - } - - #[test] - fn set_minimum_active_stake_zero_correct() { - ExtBuilder::default().has_stakers(false).build_and_execute(|| { - assert_ok!(::electing_voters(None)); - assert_eq!(MinimumActiveStake::::get(), 0); - }); - } - - #[test] - fn voters_include_self_vote() { - ExtBuilder::default().nominate(false).build_and_execute(|| { - assert!(>::iter().map(|(x, _)| x).all(|v| Staking::electing_voters( - None - ) - .unwrap() - .into_iter() - .any(|(w, _, t)| { v == w && t[0] == w }))) - }) - } - - #[test] - fn respects_snapshot_len_limits() { - ExtBuilder::default() - .set_status(41, StakerStatus::Validator) - .build_and_execute(|| { - // sum of all nominators who'd be voters (1), plus the self-votes (4). - assert_eq!(::VoterList::count(), 5); - - // if limits is less.. - assert_eq!(Staking::electing_voters(Some(1)).unwrap().len(), 1); - - // if limit is equal.. - assert_eq!(Staking::electing_voters(Some(5)).unwrap().len(), 5); - - // if limit is more. - assert_eq!(Staking::electing_voters(Some(55)).unwrap().len(), 5); - - // if target limit is more.. - assert_eq!(Staking::electable_targets(Some(6)).unwrap().len(), 4); - assert_eq!(Staking::electable_targets(Some(4)).unwrap().len(), 4); - - // if target limit is less, then we return an error. - assert_eq!( - Staking::electable_targets(Some(1)).unwrap_err(), - "Target snapshot too big" - ); - }); - } - - // Tests the criteria that in `ElectionDataProvider::voters` function, we try to get at most - // `maybe_max_len` voters, and if some of them end up being skipped, we iterate at most `2 * - // maybe_max_len`. - #[test] - fn only_iterates_max_2_times_max_allowed_len() { - ExtBuilder::default() - .nominate(false) - // the best way to invalidate a bunch of nominators is to have them nominate a lot of - // ppl, but then lower the MaxNomination limit. - .add_staker( - 61, - 61, - 2_000, - StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), - ) - .add_staker( - 71, - 71, - 2_000, - StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), - ) - .add_staker( - 81, - 81, - 2_000, - StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), - ) - .build_and_execute(|| { - // all voters ordered by stake, - assert_eq!( - ::VoterList::iter().collect::>(), - vec![61, 71, 81, 11, 21, 31] - ); - - MaxNominations::set(2); - - // we want 2 voters now, and in maximum we allow 4 iterations. This is what happens: - // 61 is pruned; - // 71 is pruned; - // 81 is pruned; - // 11 is taken; - // we finish since the 2x limit is reached. - assert_eq!( - Staking::electing_voters(Some(2)) - .unwrap() - .iter() - .map(|(stash, _, _)| stash) - .copied() - .collect::>(), - vec![11], - ); - }); - } - - #[test] - fn estimate_next_election_works() { - ExtBuilder::default().session_per_era(5).period(5).build_and_execute(|| { - // first session is always length 0. - for b in 1..20 { - run_to_block(b); - assert_eq!(Staking::next_election_prediction(System::block_number()), 20); - } - - // election - run_to_block(20); - assert_eq!(Staking::next_election_prediction(System::block_number()), 45); - assert_eq!(staking_events().len(), 1); - assert_eq!(*staking_events().last().unwrap(), Event::StakersElected); - - for b in 21..45 { - run_to_block(b); - assert_eq!(Staking::next_election_prediction(System::block_number()), 45); - } - - // election - run_to_block(45); - assert_eq!(Staking::next_election_prediction(System::block_number()), 70); - assert_eq!(staking_events().len(), 3); - assert_eq!(*staking_events().last().unwrap(), Event::StakersElected); - - Staking::force_no_eras(RuntimeOrigin::root()).unwrap(); - assert_eq!(Staking::next_election_prediction(System::block_number()), u64::MAX); - - Staking::force_new_era_always(RuntimeOrigin::root()).unwrap(); - assert_eq!(Staking::next_election_prediction(System::block_number()), 45 + 5); - - Staking::force_new_era(RuntimeOrigin::root()).unwrap(); - assert_eq!(Staking::next_election_prediction(System::block_number()), 45 + 5); - - // Do a fail election - MinimumValidatorCount::::put(1000); - run_to_block(50); - // Election: failed, next session is a new election - assert_eq!(Staking::next_election_prediction(System::block_number()), 50 + 5); - // The new era is still forced until a new era is planned. - assert_eq!(ForceEra::::get(), Forcing::ForceNew); - - MinimumValidatorCount::::put(2); - run_to_block(55); - assert_eq!(Staking::next_election_prediction(System::block_number()), 55 + 25); - assert_eq!(staking_events().len(), 10); - assert_eq!( - *staking_events().last().unwrap(), - Event::ForceEra { mode: Forcing::NotForcing } - ); - assert_eq!( - *staking_events().get(staking_events().len() - 2).unwrap(), - Event::StakersElected - ); - // The new era has been planned, forcing is changed from `ForceNew` to `NotForcing`. - assert_eq!(ForceEra::::get(), Forcing::NotForcing); - }) - } -} - -#[test] -#[should_panic] -fn count_check_works() { - ExtBuilder::default().build_and_execute(|| { - // We should never insert into the validators or nominators map directly as this will - // not keep track of the count. This test should panic as we verify the count is accurate - // after every test using the `post_checks` in `mock`. - Validators::::insert(987654321, ValidatorPrefs::default()); - Nominators::::insert( - 987654321, - Nominations { - targets: Default::default(), - submitted_in: Default::default(), - suppressed: false, - }, - ); - }) -} - -#[test] -fn min_bond_checks_work() { - ExtBuilder::default() - .existential_deposit(100) - .balance_factor(100) - .min_nominator_bond(1_000) - .min_validator_bond(1_500) - .build_and_execute(|| { - // 500 is not enough for any role - assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 500, RewardDestination::Controller)); - assert_noop!( - Staking::nominate(RuntimeOrigin::signed(3), vec![1]), - Error::::InsufficientBond - ); - assert_noop!( - Staking::validate(RuntimeOrigin::signed(3), ValidatorPrefs::default()), - Error::::InsufficientBond, - ); - - // 1000 is enough for nominator - assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(3), 500)); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![1])); - assert_noop!( - Staking::validate(RuntimeOrigin::signed(3), ValidatorPrefs::default()), - Error::::InsufficientBond, - ); - - // 1500 is enough for validator - assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(3), 500)); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![1])); - assert_ok!(Staking::validate(RuntimeOrigin::signed(3), ValidatorPrefs::default())); - - // Can't unbond anything as validator - assert_noop!( - Staking::unbond(RuntimeOrigin::signed(3), 500), - Error::::InsufficientBond - ); - - // Once they are a nominator, they can unbond 500 - assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![1])); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(3), 500)); - assert_noop!( - Staking::unbond(RuntimeOrigin::signed(3), 500), - Error::::InsufficientBond - ); - - // Once they are chilled they can unbond everything - assert_ok!(Staking::chill(RuntimeOrigin::signed(3))); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(3), 1000)); - }) -} - -#[test] -fn chill_other_works() { - ExtBuilder::default() - .existential_deposit(100) - .balance_factor(100) - .min_nominator_bond(1_000) - .min_validator_bond(1_500) - .build_and_execute(|| { - let initial_validators = Validators::::count(); - let initial_nominators = Nominators::::count(); - for i in 0..15 { - let a = 4 * i; - let b = 4 * i + 2; - let c = 4 * i + 3; - Balances::make_free_balance_be(&a, 100_000); - Balances::make_free_balance_be(&b, 100_000); - Balances::make_free_balance_be(&c, 100_000); - - // Nominator - assert_ok!(Staking::bond( - RuntimeOrigin::signed(a), - 1000, - RewardDestination::Controller - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(a), vec![1])); - - // Validator - assert_ok!(Staking::bond( - RuntimeOrigin::signed(b), - 1500, - RewardDestination::Controller - )); - assert_ok!(Staking::validate(RuntimeOrigin::signed(b), ValidatorPrefs::default())); - } - - // To chill other users, we need to: - // * Set a minimum bond amount - // * Set a limit - // * Set a threshold - // - // If any of these are missing, we do not have enough information to allow the - // `chill_other` to succeed from one user to another. - - // Can't chill these users - assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 0), - Error::::CannotChillOther - ); - assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 2), - Error::::CannotChillOther - ); - - // Change the minimum bond... but no limits. - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - ConfigOp::Set(1_500), - ConfigOp::Set(2_000), - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove - )); - - // Still can't chill these users - assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 0), - Error::::CannotChillOther - ); - assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 2), - Error::::CannotChillOther - ); - - // Add limits, but no threshold - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - ConfigOp::Noop, - ConfigOp::Noop, - ConfigOp::Set(10), - ConfigOp::Set(10), - ConfigOp::Noop, - ConfigOp::Noop - )); - - // Still can't chill these users - assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 0), - Error::::CannotChillOther - ); - assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 2), - Error::::CannotChillOther - ); - - // Add threshold, but no limits - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - ConfigOp::Noop, - ConfigOp::Noop, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Noop, - ConfigOp::Noop - )); - - // Still can't chill these users - assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 0), - Error::::CannotChillOther - ); - assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 2), - Error::::CannotChillOther - ); - - // Add threshold and limits - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - ConfigOp::Noop, - ConfigOp::Noop, - ConfigOp::Set(10), - ConfigOp::Set(10), - ConfigOp::Set(Percent::from_percent(75)), - ConfigOp::Noop - )); - - // 16 people total because tests start with 2 active one - assert_eq!(Nominators::::count(), 15 + initial_nominators); - assert_eq!(Validators::::count(), 15 + initial_validators); - - // Users can now be chilled down to 7 people, so we try to remove 9 of them (starting - // with 16) - for i in 6..15 { - let b = 4 * i; - let d = 4 * i + 2; - assert_ok!(Staking::chill_other(RuntimeOrigin::signed(1337), b)); - assert_ok!(Staking::chill_other(RuntimeOrigin::signed(1337), d)); - } - - // chill a nominator. Limit is not reached, not chill-able - assert_eq!(Nominators::::count(), 7); - assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 0), - Error::::CannotChillOther - ); - // chill a validator. Limit is reached, chill-able. - assert_eq!(Validators::::count(), 9); - assert_ok!(Staking::chill_other(RuntimeOrigin::signed(1337), 2)); - }) -} - -#[test] -fn capped_stakers_works() { - ExtBuilder::default().build_and_execute(|| { - let validator_count = Validators::::count(); - assert_eq!(validator_count, 3); - let nominator_count = Nominators::::count(); - assert_eq!(nominator_count, 1); - - // Change the maximums - let max = 10; - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - ConfigOp::Set(10), - ConfigOp::Set(10), - ConfigOp::Set(max), - ConfigOp::Set(max), - ConfigOp::Remove, - ConfigOp::Remove, - )); - - // can create `max - validator_count` validators - let mut some_existing_validator = AccountId::default(); - for i in 0..max - validator_count { - let (_, controller) = testing_utils::create_stash_controller::( - i + 10_000_000, - 100, - RewardDestination::Controller, - ) - .unwrap(); - assert_ok!(Staking::validate( - RuntimeOrigin::signed(controller), - ValidatorPrefs::default() - )); - some_existing_validator = controller; - } - - // but no more - let (_, last_validator) = testing_utils::create_stash_controller::( - 1337, - 100, - RewardDestination::Controller, - ) - .unwrap(); - - assert_noop!( - Staking::validate(RuntimeOrigin::signed(last_validator), ValidatorPrefs::default()), - Error::::TooManyValidators, - ); - - // same with nominators - let mut some_existing_nominator = AccountId::default(); - for i in 0..max - nominator_count { - let (_, controller) = testing_utils::create_stash_controller::( - i + 20_000_000, - 100, - RewardDestination::Controller, - ) - .unwrap(); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(controller), vec![1])); - some_existing_nominator = controller; - } - - // one more is too many. - let (_, last_nominator) = testing_utils::create_stash_controller::( - 30_000_000, - 100, - RewardDestination::Controller, - ) - .unwrap(); - assert_noop!( - Staking::nominate(RuntimeOrigin::signed(last_nominator), vec![1]), - Error::::TooManyNominators - ); - - // Re-nominate works fine - assert_ok!(Staking::nominate(RuntimeOrigin::signed(some_existing_nominator), vec![1])); - // Re-validate works fine - assert_ok!(Staking::validate( - RuntimeOrigin::signed(some_existing_validator), - ValidatorPrefs::default() - )); - - // No problem when we set to `None` again - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - ConfigOp::Noop, - ConfigOp::Noop, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Noop, - ConfigOp::Noop, - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(last_nominator), vec![1])); - assert_ok!(Staking::validate( - RuntimeOrigin::signed(last_validator), - ValidatorPrefs::default() - )); - }) -} - -#[test] -fn min_commission_works() { - ExtBuilder::default().build_and_execute(|| { - // account 11 controls the stash of itself. - assert_ok!(Staking::validate( - RuntimeOrigin::signed(11), - ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } - )); - - // event emitted should be correct - assert_eq!( - *staking_events().last().unwrap(), - Event::ValidatorPrefsSet { - stash: 11, - prefs: ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } - } - ); - - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Set(Perbill::from_percent(10)), - )); - - // can't make it less than 10 now - assert_noop!( - Staking::validate( - RuntimeOrigin::signed(11), - ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } - ), - Error::::CommissionTooLow - ); - - // can only change to higher. - assert_ok!(Staking::validate( - RuntimeOrigin::signed(11), - ValidatorPrefs { commission: Perbill::from_percent(10), blocked: false } - )); - - assert_ok!(Staking::validate( - RuntimeOrigin::signed(11), - ValidatorPrefs { commission: Perbill::from_percent(15), blocked: false } - )); - }) -} - -#[test] -fn change_of_max_nominations() { - use frame_election_provider_support::ElectionDataProvider; - ExtBuilder::default() - .add_staker(61, 61, 10, StakerStatus::Nominator(vec![1])) - .add_staker(71, 71, 10, StakerStatus::Nominator(vec![1, 2, 3])) - .balance_factor(10) - .build_and_execute(|| { - // pre-condition - assert_eq!(MaxNominations::get(), 16); - - assert_eq!( - Nominators::::iter() - .map(|(k, n)| (k, n.targets.len())) - .collect::>(), - vec![(101, 2), (71, 3), (61, 1)] - ); - // 3 validators and 3 nominators - assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3); - - // abrupt change from 16 to 4, everyone should be fine. - MaxNominations::set(4); - - assert_eq!( - Nominators::::iter() - .map(|(k, n)| (k, n.targets.len())) - .collect::>(), - vec![(101, 2), (71, 3), (61, 1)] - ); - assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3); - - // abrupt change from 4 to 3, everyone should be fine. - MaxNominations::set(3); - - assert_eq!( - Nominators::::iter() - .map(|(k, n)| (k, n.targets.len())) - .collect::>(), - vec![(101, 2), (71, 3), (61, 1)] - ); - assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3); - - // abrupt change from 3 to 2, this should cause some nominators to be non-decodable, and - // thus non-existent unless if they update. - MaxNominations::set(2); - - assert_eq!( - Nominators::::iter() - .map(|(k, n)| (k, n.targets.len())) - .collect::>(), - vec![(101, 2), (61, 1)] - ); - // 70 is still in storage.. - assert!(Nominators::::contains_key(71)); - // but its value cannot be decoded and default is returned. - assert!(Nominators::::get(71).is_none()); - - assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 2); - assert!(Nominators::::contains_key(101)); - - // abrupt change from 2 to 1, this should cause some nominators to be non-decodable, and - // thus non-existent unless if they update. - MaxNominations::set(1); - - assert_eq!( - Nominators::::iter() - .map(|(k, n)| (k, n.targets.len())) - .collect::>(), - vec![(61, 1)] - ); - assert!(Nominators::::contains_key(71)); - assert!(Nominators::::contains_key(61)); - assert!(Nominators::::get(71).is_none()); - assert!(Nominators::::get(61).is_some()); - assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 1); - - // now one of them can revive themselves by re-nominating to a proper value. - assert_ok!(Staking::nominate(RuntimeOrigin::signed(71), vec![1])); - assert_eq!( - Nominators::::iter() - .map(|(k, n)| (k, n.targets.len())) - .collect::>(), - vec![(71, 1), (61, 1)] - ); - - // or they can be chilled by any account. - assert!(Nominators::::contains_key(101)); - assert!(Nominators::::get(101).is_none()); - assert_ok!(Staking::chill_other(RuntimeOrigin::signed(71), 101)); - assert!(!Nominators::::contains_key(101)); - assert!(Nominators::::get(101).is_none()); - }) -} - -mod sorted_list_provider { - use frame_election_provider_support::SortedListProvider; - - use super::*; - - #[test] - fn re_nominate_does_not_change_counters_or_list() { - ExtBuilder::default().nominate(true).build_and_execute(|| { - // given - let pre_insert_voter_count = - (Nominators::::count() + Validators::::count()) as u32; - assert_eq!(::VoterList::count(), pre_insert_voter_count); - - assert_eq!( - ::VoterList::iter().collect::>(), - vec![11, 21, 31, 101] - ); - - // when account 101 renominates - assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![41])); - - // then counts don't change - assert_eq!(::VoterList::count(), pre_insert_voter_count); - // and the list is the same - assert_eq!( - ::VoterList::iter().collect::>(), - vec![11, 21, 31, 101] - ); - }); - } - - #[test] - fn re_validate_does_not_change_counters_or_list() { - ExtBuilder::default().nominate(false).build_and_execute(|| { - // given - let pre_insert_voter_count = - (Nominators::::count() + Validators::::count()) as u32; - assert_eq!(::VoterList::count(), pre_insert_voter_count); - - assert_eq!(::VoterList::iter().collect::>(), vec![11, 21, 31]); - - // when account 11 re-validates - assert_ok!(Staking::validate(RuntimeOrigin::signed(11), Default::default())); - - // then counts don't change - assert_eq!(::VoterList::count(), pre_insert_voter_count); - // and the list is the same - assert_eq!(::VoterList::iter().collect::>(), vec![11, 21, 31]); - }); - } -} - -#[test] -fn force_apply_min_commission_works() { - let prefs = |c| ValidatorPrefs { commission: Perbill::from_percent(c), blocked: false }; - let validators = || Validators::::iter().collect::>(); - ExtBuilder::default().build_and_execute(|| { - assert_ok!(Staking::validate(RuntimeOrigin::signed(31), prefs(10))); - assert_ok!(Staking::validate(RuntimeOrigin::signed(21), prefs(5))); - - // Given - assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(0))]); - MinCommission::::set(Perbill::from_percent(5)); - - // When applying to a commission greater than min - assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 31)); - // Then the commission is not changed - assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(0))]); - - // When applying to a commission that is equal to min - assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 21)); - // Then the commission is not changed - assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(0))]); - - // When applying to a commission that is less than the min - assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 11)); - // Then the commission is bumped to the min - assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(5))]); - - // When applying commission to a validator that doesn't exist then storage is not altered - assert_noop!( - Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 420), - Error::::NotStash - ); - }); -} - -#[test] -fn proportional_slash_stop_slashing_if_remaining_zero() { - let c = |era, value| UnlockChunk:: { era, value }; - // Given - let mut ledger = StakingLedger:: { - stash: 123, - total: 40, - active: 20, - // we have some chunks, but they are not affected. - unlocking: bounded_vec![c(1, 10), c(2, 10)], - claimed_rewards: bounded_vec![], - }; - - assert_eq!(BondingDuration::get(), 3); - - // should not slash more than the amount requested, by accidentally slashing the first chunk. - assert_eq!(ledger.slash(18, 1, 0), 18); -} - -#[test] -fn proportional_ledger_slash_works() { - let c = |era, value| UnlockChunk:: { era, value }; - // Given - let mut ledger = StakingLedger:: { - stash: 123, - total: 10, - active: 10, - unlocking: bounded_vec![], - claimed_rewards: bounded_vec![], - }; - assert_eq!(BondingDuration::get(), 3); - - // When we slash a ledger with no unlocking chunks - assert_eq!(ledger.slash(5, 1, 0), 5); - // Then - assert_eq!(ledger.total, 5); - assert_eq!(ledger.active, 5); - assert_eq!(LedgerSlashPerEra::get().0, 5); - assert_eq!(LedgerSlashPerEra::get().1, Default::default()); - - // When we slash a ledger with no unlocking chunks and the slash amount is greater then the - // total - assert_eq!(ledger.slash(11, 1, 0), 5); - // Then - assert_eq!(ledger.total, 0); - assert_eq!(ledger.active, 0); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, Default::default()); - - // Given - ledger.unlocking = bounded_vec![c(4, 10), c(5, 10)]; - ledger.total = 2 * 10; - ledger.active = 0; - // When all the chunks overlap with the slash eras - assert_eq!(ledger.slash(20, 0, 0), 20); - // Then - assert_eq!(ledger.unlocking, vec![]); - assert_eq!(ledger.total, 0); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)])); - - // Given - ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; - ledger.total = 4 * 100; - ledger.active = 0; - // When the first 2 chunks don't overlap with the affected range of unlock eras. - assert_eq!(ledger.slash(140, 0, 3), 140); - // Then - assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]); - assert_eq!(ledger.total, 4 * 100 - 140); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 30), (7, 30)])); - - // Given - ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; - ledger.total = 4 * 100; - ledger.active = 0; - // When the first 2 chunks don't overlap with the affected range of unlock eras. - assert_eq!(ledger.slash(15, 0, 3), 15); - // Then - assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 100 - 8), c(7, 100 - 7)]); - assert_eq!(ledger.total, 4 * 100 - 15); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 92), (7, 93)])); - - // Given - ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; - ledger.active = 500; - // 900 - ledger.total = 40 + 10 + 100 + 250 + 500; - // When we have a partial slash that touches all chunks - assert_eq!(ledger.slash(900 / 2, 0, 0), 450); - // Then - assert_eq!(ledger.active, 500 / 2); - assert_eq!(ledger.unlocking, vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)]); - assert_eq!(ledger.total, 900 / 2); - assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); - assert_eq!( - LedgerSlashPerEra::get().1, - BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)]) - ); - - // slash 1/4th with not chunk. - ledger.unlocking = bounded_vec![]; - ledger.active = 500; - ledger.total = 500; - // When we have a partial slash that touches all chunks - assert_eq!(ledger.slash(500 / 4, 0, 0), 500 / 4); - // Then - assert_eq!(ledger.active, 3 * 500 / 4); - assert_eq!(ledger.unlocking, vec![]); - assert_eq!(ledger.total, ledger.active); - assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4); - assert_eq!(LedgerSlashPerEra::get().1, Default::default()); - - // Given we have the same as above, - ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; - ledger.active = 500; - ledger.total = 40 + 10 + 100 + 250 + 500; // 900 - assert_eq!(ledger.total, 900); - // When we have a higher min balance - assert_eq!( - ledger.slash( - 900 / 2, - 25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it to - * get swept */ - 0 - ), - 450 - ); - assert_eq!(ledger.active, 500 / 2); - // the last chunk was not slashed 50% like all the rest, because some other earlier chunks got - // dusted. - assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 150)]); - assert_eq!(ledger.total, 900 / 2); - assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); - assert_eq!( - LedgerSlashPerEra::get().1, - BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 150)]) - ); - - // Given - // slash order --------------------NA--------2----------0----------1---- - ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; - ledger.active = 500; - ledger.total = 40 + 10 + 100 + 250 + 500; // 900 - assert_eq!( - ledger.slash( - 500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2 - 0, - 3 /* slash era 6 first, so the affected parts are era 6, era 7 and - * ledge.active. This will cause the affected to go to zero, and then we will - * start slashing older chunks */ - ), - 500 + 250 + 10 + 100 / 2 - ); - // Then - assert_eq!(ledger.active, 0); - assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2)]); - assert_eq!(ledger.total, 90); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)])); - - // Given - // iteration order------------------NA---------2----------0----------1---- - ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; - ledger.active = 100; - ledger.total = 5 * 100; - // When - assert_eq!( - ledger.slash( - 351, // active + era 6 + era 7 + era 5 / 2 + 1 - 50, // min balance - everything slashed below 50 will get dusted - 3 /* slash era 3+3 first, so the affected parts are era 6, era 7 and - * ledge.active. This will cause the affected to go to zero, and then we will - * start slashing older chunks */ - ), - 400 - ); - // Then - assert_eq!(ledger.active, 0); - assert_eq!(ledger.unlocking, vec![c(4, 100)]); - assert_eq!(ledger.total, 100); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)])); - - // Tests for saturating arithmetic - - // Given - let slash = u64::MAX as Balance * 2; - // The value of the other parts of ledger that will get slashed - let value = slash - (10 * 4); - - ledger.active = 10; - ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)]; - ledger.total = value + 40; - // When - let slash_amount = ledger.slash(slash, 0, 0); - assert_eq_error_rate!(slash_amount, slash, 5); - // Then - assert_eq!(ledger.active, 0); // slash of 9 - assert_eq!(ledger.unlocking, vec![]); - assert_eq!(ledger.total, 0); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0), (6, 0), (7, 0)])); - - // Given - use sp_runtime::PerThing as _; - let slash = u64::MAX as Balance * 2; - let value = u64::MAX as Balance * 2; - let unit = 100; - // slash * value that will saturate - assert!(slash.checked_mul(value).is_none()); - // but slash * unit won't. - assert!(slash.checked_mul(unit).is_some()); - ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)]; - //--------------------------------------note value^^^ - ledger.active = unit; - ledger.total = unit * 4 + value; - // When - assert_eq!(ledger.slash(slash, 0, 0), slash); - // Then - // The amount slashed out of `unit` - let affected_balance = value + unit * 4; - let ratio = - Perquintill::from_rational_with_rounding(slash, affected_balance, Rounding::Up).unwrap(); - // `unit` after the slash is applied - let unit_slashed = { - let unit_slash = ratio.mul_ceil(unit); - unit - unit_slash - }; - let value_slashed = { - let value_slash = ratio.mul_ceil(value); - value - value_slash - }; - assert_eq!(ledger.active, unit_slashed); - assert_eq!(ledger.unlocking, vec![c(5, value_slashed), c(7, 32)]); - assert_eq!(ledger.total, value_slashed + 32); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!( - LedgerSlashPerEra::get().1, - BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 32)]) - ); -} - -#[test] -fn pre_bonding_era_cannot_be_claimed() { - // Verifies initial conditions of mock - ExtBuilder::default().nominate(false).build_and_execute(|| { - let history_depth = HistoryDepth::get(); - // jump to some era above history_depth - let mut current_era = history_depth + 10; - let last_reward_era = current_era - 1; - let start_reward_era = current_era - history_depth; - - // put some money in stash=3 and controller=4. - for i in 3..5 { - let _ = Balances::make_free_balance_be(&i, 2000); - } - - mock::start_active_era(current_era); - - // add a new candidate for being a validator. account 3 controlled by 4. - assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 1500, RewardDestination::Controller)); - - let claimed_rewards: BoundedVec<_, _> = - (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); - assert_eq!( - Staking::ledger(&3).unwrap(), - StakingLedger { - stash: 3, - total: 1500, - active: 1500, - unlocking: Default::default(), - claimed_rewards, - } - ); - - // start next era - current_era = current_era + 1; - mock::start_active_era(current_era); - - // claiming reward for last era in which validator was active works - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(3), 3, current_era - 1)); - - // consumed weight for all payout_stakers dispatches that fail - let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); - // cannot claim rewards for an era before bonding occured as it is - // already marked as claimed. - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(3), 3, current_era - 2), - Error::::AlreadyClaimed.with_weight(err_weight) - ); - - // decoding will fail now since Staking Ledger is in corrupt state - HistoryDepth::set(history_depth - 1); - assert_eq!(Staking::ledger(&4), None); - - // make sure stakers still cannot claim rewards that they are not meant to - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(3), 3, current_era - 2), - Error::::NotController - ); - - // fix the corrupted state for post conditions check - HistoryDepth::set(history_depth); - }); -} - -#[test] -fn reducing_history_depth_abrupt() { - // Verifies initial conditions of mock - ExtBuilder::default().nominate(false).build_and_execute(|| { - let original_history_depth = HistoryDepth::get(); - let mut current_era = original_history_depth + 10; - let last_reward_era = current_era - 1; - let start_reward_era = current_era - original_history_depth; - - // put some money in (stash, controller)=(3,3),(5,5). - for i in 3..7 { - let _ = Balances::make_free_balance_be(&i, 2000); - } - - // start current era - mock::start_active_era(current_era); - - // add a new candidate for being a staker. account 3 controlled by 3. - assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 1500, RewardDestination::Controller)); - - // all previous era before the bonding action should be marked as - // claimed. - let claimed_rewards: BoundedVec<_, _> = - (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); - assert_eq!( - Staking::ledger(&3).unwrap(), - StakingLedger { - stash: 3, - total: 1500, - active: 1500, - unlocking: Default::default(), - claimed_rewards, - } - ); - - // next era - current_era = current_era + 1; - mock::start_active_era(current_era); - - // claiming reward for last era in which validator was active works - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(3), 3, current_era - 1)); - - // next era - current_era = current_era + 1; - mock::start_active_era(current_era); - - // history_depth reduced without migration - let history_depth = original_history_depth - 1; - HistoryDepth::set(history_depth); - // claiming reward does not work anymore - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(3), 3, current_era - 1), - Error::::NotController - ); - - // new stakers can still bond - assert_ok!(Staking::bond(RuntimeOrigin::signed(5), 1200, RewardDestination::Controller)); - - // new staking ledgers created will be bounded by the current history depth - let last_reward_era = current_era - 1; - let start_reward_era = current_era - history_depth; - let claimed_rewards: BoundedVec<_, _> = - (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); - assert_eq!( - Staking::ledger(&5).unwrap(), - StakingLedger { - stash: 5, - total: 1200, - active: 1200, - unlocking: Default::default(), - claimed_rewards, - } - ); - - // fix the corrupted state for post conditions check - HistoryDepth::set(original_history_depth); - }); -} - -#[test] -fn reducing_max_unlocking_chunks_abrupt() { - // Concern is on validators only - // By Default 11, 10 are stash and ctrl and 21,20 - ExtBuilder::default().build_and_execute(|| { - // given a staker at era=10 and MaxUnlockChunks set to 2 - MaxUnlockingChunks::set(2); - start_active_era(10); - assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 300, RewardDestination::Staked)); - assert!(matches!(Staking::ledger(3), Some(_))); - - // when staker unbonds - assert_ok!(Staking::unbond(RuntimeOrigin::signed(3), 20)); - - // then an unlocking chunk is added at `current_era + bonding_duration` - // => 10 + 3 = 13 - let expected_unlocking: BoundedVec, MaxUnlockingChunks> = - bounded_vec![UnlockChunk { value: 20 as Balance, era: 13 as EraIndex }]; - assert!(matches!(Staking::ledger(3), - Some(StakingLedger { - unlocking, - .. - }) if unlocking==expected_unlocking)); - - // when staker unbonds at next era - start_active_era(11); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(3), 50)); - // then another unlock chunk is added - let expected_unlocking: BoundedVec, MaxUnlockingChunks> = - bounded_vec![UnlockChunk { value: 20, era: 13 }, UnlockChunk { value: 50, era: 14 }]; - assert!(matches!(Staking::ledger(3), - Some(StakingLedger { - unlocking, - .. - }) if unlocking==expected_unlocking)); - - // when staker unbonds further - start_active_era(12); - // then further unbonding not possible - assert_noop!(Staking::unbond(RuntimeOrigin::signed(3), 20), Error::::NoMoreChunks); - - // when max unlocking chunks is reduced abruptly to a low value - MaxUnlockingChunks::set(1); - // then unbond, rebond ops are blocked with ledger in corrupt state - assert_noop!(Staking::unbond(RuntimeOrigin::signed(3), 20), Error::::NotController); - assert_noop!(Staking::rebond(RuntimeOrigin::signed(3), 100), Error::::NotController); - - // reset the ledger corruption - MaxUnlockingChunks::set(2); - }) -} - -#[test] -fn cannot_set_unsupported_validator_count() { - ExtBuilder::default().build_and_execute(|| { - MaxWinners::set(50); - // set validator count works - assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 30)); - assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 50)); - // setting validator count above 100 does not work - assert_noop!( - Staking::set_validator_count(RuntimeOrigin::root(), 51), - Error::::TooManyValidators, - ); - }) -} - -#[test] -fn increase_validator_count_errors() { - ExtBuilder::default().build_and_execute(|| { - MaxWinners::set(50); - assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 40)); - - // increase works - assert_ok!(Staking::increase_validator_count(RuntimeOrigin::root(), 6)); - assert_eq!(ValidatorCount::::get(), 46); - - // errors - assert_noop!( - Staking::increase_validator_count(RuntimeOrigin::root(), 5), - Error::::TooManyValidators, - ); - }) -} - -#[test] -fn scale_validator_count_errors() { - ExtBuilder::default().build_and_execute(|| { - MaxWinners::set(50); - assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 20)); - - // scale value works - assert_ok!(Staking::scale_validator_count( - RuntimeOrigin::root(), - Percent::from_percent(200) - )); - assert_eq!(ValidatorCount::::get(), 40); - - // errors - assert_noop!( - Staking::scale_validator_count(RuntimeOrigin::root(), Percent::from_percent(126)), - Error::::TooManyValidators, - ); - }) -} - -#[test] -fn set_min_commission_works_with_admin_origin() { - ExtBuilder::default().build_and_execute(|| { - // no minimum commission set initially - assert_eq!(MinCommission::::get(), Zero::zero()); - - // root can set min commission - assert_ok!(Staking::set_min_commission(RuntimeOrigin::root(), Perbill::from_percent(10))); - - assert_eq!(MinCommission::::get(), Perbill::from_percent(10)); - - // Non privileged origin can not set min_commission - assert_noop!( - Staking::set_min_commission(RuntimeOrigin::signed(2), Perbill::from_percent(15)), - BadOrigin - ); - - // Admin Origin can set min commission - assert_ok!(Staking::set_min_commission( - RuntimeOrigin::signed(1), - Perbill::from_percent(15), - )); - - // setting commission below min_commission fails - assert_noop!( - Staking::validate( - RuntimeOrigin::signed(11), - ValidatorPrefs { commission: Perbill::from_percent(14), blocked: false } - ), - Error::::CommissionTooLow - ); - - // setting commission >= min_commission works - assert_ok!(Staking::validate( - RuntimeOrigin::signed(11), - ValidatorPrefs { commission: Perbill::from_percent(15), blocked: false } - )); - }) -} - -mod staking_interface { - use frame_support::storage::with_storage_layer; - use sp_staking::StakingInterface; - - use super::*; - - #[test] - fn force_unstake_with_slash_works() { - ExtBuilder::default().build_and_execute(|| { - // without slash - let _ = with_storage_layer::<(), _, _>(|| { - // bond an account, can unstake - assert_eq!(Staking::bonded(&11), Some(11)); - assert_ok!(::force_unstake(11)); - Err(DispatchError::from("revert")) - }); - - // bond again and add a slash, still can unstake. - assert_eq!(Staking::bonded(&11), Some(11)); - add_slash(&11); - assert_ok!(::force_unstake(11)); - }); - } - - #[test] - fn do_withdraw_unbonded_with_wrong_slash_spans_works_as_expected() { - ExtBuilder::default().build_and_execute(|| { - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], - }], - &[Perbill::from_percent(100)], - ); - - assert_eq!(Staking::bonded(&11), Some(11)); - - assert_noop!( - Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0), - Error::::IncorrectSlashingSpans - ); - - let num_slashing_spans = Staking::slashing_spans(&11).map_or(0, |s| s.iter().count()); - assert_ok!(Staking::withdraw_unbonded( - RuntimeOrigin::signed(11), - num_slashing_spans as u32 - )); - }); - } - - #[test] - fn status() { - ExtBuilder::default().build_and_execute(|| { - // stash of a validator is identified as a validator - assert_eq!(Staking::status(&11).unwrap(), StakerStatus::Validator); - // .. but not the controller. - assert!(Staking::status(&10).is_err()); - - // stash of nominator is identified as a nominator - assert_eq!(Staking::status(&101).unwrap(), StakerStatus::Nominator(vec![11, 21])); - // .. but not the controller. - assert!(Staking::status(&100).is_err()); - - // stash of chilled is identified as a chilled - assert_eq!(Staking::status(&41).unwrap(), StakerStatus::Idle); - // .. but not the controller. - assert!(Staking::status(&40).is_err()); - - // random other account. - assert!(Staking::status(&42).is_err()); - }) - } -} diff --git a/pallets/staking/src/weights.rs b/pallets/staking/src/weights.rs deleted file mode 100644 index f2c65e677..000000000 --- a/pallets/staking/src/weights.rs +++ /dev/null @@ -1,1506 +0,0 @@ -// 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. - -//! Autogenerated weights for pallet_staking -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/substrate -// benchmark -// pallet -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_staking -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/staking/src/weights.rs -// --header=./HEADER-APACHE2 -// --template=./.maintain/frame-weight-template.hbs - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use core::marker::PhantomData; - -/// Weight functions needed for pallet_staking. -pub trait WeightInfo { - fn bond() -> Weight; - fn bond_extra() -> Weight; - fn unbond() -> Weight; - fn withdraw_unbonded_update(s: u32, ) -> Weight; - fn withdraw_unbonded_kill(s: u32, ) -> Weight; - fn validate() -> Weight; - fn kick(k: u32, ) -> Weight; - fn nominate(n: u32, ) -> Weight; - fn chill() -> Weight; - fn set_payee() -> Weight; - fn set_controller() -> Weight; - fn set_validator_count() -> Weight; - fn force_no_eras() -> Weight; - fn force_new_era() -> Weight; - fn force_new_era_always() -> Weight; - fn set_invulnerables(v: u32, ) -> Weight; - fn force_unstake(s: u32, ) -> Weight; - fn cancel_deferred_slash(s: u32, ) -> Weight; - fn payout_stakers_dead_controller(n: u32, ) -> Weight; - fn payout_stakers_alive_staked(n: u32, ) -> Weight; - fn rebond(l: u32, ) -> Weight; - fn reap_stash(s: u32, ) -> Weight; - fn new_era(v: u32, n: u32, ) -> Weight; - fn get_npos_voters(v: u32, n: u32, ) -> Weight; - fn get_npos_targets(v: u32, ) -> Weight; - fn set_staking_configs_all_set() -> Weight; - fn set_staking_configs_all_remove() -> Weight; - fn chill_other() -> Weight; - fn force_apply_min_commission() -> Weight; - fn set_min_commission() -> Weight; -} - -/// Weights for pallet_staking using the Substrate node and recommended hardware. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - fn bond() -> Weight { - // Proof Size summary in bytes: - // Measured: `1047` - // Estimated: `4764` - // Minimum execution time: 53_983_000 picoseconds. - Weight::from_parts(55_296_000, 4764) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) - } - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:3 w:3) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:2 w:2) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - fn bond_extra() -> Weight { - // Proof Size summary in bytes: - // Measured: `2028` - // Estimated: `8877` - // Minimum execution time: 96_590_000 picoseconds. - Weight::from_parts(98_921_000, 8877) - .saturating_add(T::DbWeight::get().reads(9_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) - } - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:0) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:1 w:0) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:3 w:3) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:2 w:2) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - fn unbond() -> Weight { - // Proof Size summary in bytes: - // Measured: `2233` - // Estimated: `8877` - // Minimum execution time: 99_901_000 picoseconds. - Weight::from_parts(102_919_000, 8877) - .saturating_add(T::DbWeight::get().reads(12_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) - } - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// The range of component `s` is `[0, 100]`. - fn withdraw_unbonded_update(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1021` - // Estimated: `4764` - // Minimum execution time: 45_230_000 picoseconds. - Weight::from_parts(47_052_829, 4764) - // Standard Error: 1_044 - .saturating_add(Weight::from_parts(43_887, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking SlashingSpans (r:1 w:1) - /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: Staking SpanSlash (r:0 w:100) - /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) - /// The range of component `s` is `[0, 100]`. - fn withdraw_unbonded_kill(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `2294 + s * (4 ±0)` - // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 97_534_000 picoseconds. - Weight::from_parts(104_772_163, 6248) - // Standard Error: 3_674 - .saturating_add(Weight::from_parts(1_470_124, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(13_u64)) - .saturating_add(T::DbWeight::get().writes(11_u64)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) - .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) - } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking MinValidatorBond (r:1 w:0) - /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking MinCommission (r:1 w:0) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1 w:1) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking MaxValidatorsCount (r:1 w:0) - /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:0) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:1 w:1) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking CounterForValidators (r:1 w:1) - /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn validate() -> Weight { - // Proof Size summary in bytes: - // Measured: `1414` - // Estimated: `4556` - // Minimum execution time: 57_467_000 picoseconds. - Weight::from_parts(59_437_000, 4556) - .saturating_add(T::DbWeight::get().reads(11_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) - } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:128 w:128) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// The range of component `k` is `[1, 128]`. - fn kick(k: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1285 + k * (569 ±0)` - // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 32_857_000 picoseconds. - Weight::from_parts(37_116_967, 4556) - // Standard Error: 9_522 - .saturating_add(Weight::from_parts(8_796_167, 0).saturating_mul(k.into())) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) - .saturating_add(Weight::from_parts(0, 3033).saturating_mul(k.into())) - } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:1 w:0) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking MaxNominatorsCount (r:1 w:0) - /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:17 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// The range of component `n` is `[1, 16]`. - fn nominate(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1908 + n * (102 ±0)` - // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 69_613_000 picoseconds. - Weight::from_parts(68_079_061, 6248) - // Standard Error: 18_554 - .saturating_add(Weight::from_parts(4_012_761, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(12_u64)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(6_u64)) - .saturating_add(Weight::from_parts(0, 2520).saturating_mul(n.into())) - } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn chill() -> Weight { - // Proof Size summary in bytes: - // Measured: `1748` - // Estimated: `6248` - // Minimum execution time: 60_430_000 picoseconds. - Weight::from_parts(62_702_000, 6248) - .saturating_add(T::DbWeight::get().reads(8_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) - } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - fn set_payee() -> Weight { - // Proof Size summary in bytes: - // Measured: `808` - // Estimated: `4556` - // Minimum execution time: 14_276_000 picoseconds. - Weight::from_parts(14_766_000, 4556) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:2 w:2) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - fn set_controller() -> Weight { - // Proof Size summary in bytes: - // Measured: `907` - // Estimated: `8122` - // Minimum execution time: 21_710_000 picoseconds. - Weight::from_parts(22_430_000, 8122) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } - /// Storage: Staking ValidatorCount (r:0 w:1) - /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn set_validator_count() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_970_000 picoseconds. - Weight::from_parts(3_120_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: Staking ForceEra (r:0 w:1) - /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - fn force_no_eras() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 9_362_000 picoseconds. - Weight::from_parts(9_785_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: Staking ForceEra (r:0 w:1) - /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - fn force_new_era() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 9_275_000 picoseconds. - Weight::from_parts(9_678_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: Staking ForceEra (r:0 w:1) - /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - fn force_new_era_always() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 9_414_000 picoseconds. - Weight::from_parts(9_848_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: Staking Invulnerables (r:0 w:1) - /// Proof Skipped: Staking Invulnerables (max_values: Some(1), max_size: None, mode: Measured) - /// The range of component `v` is `[0, 1000]`. - fn set_invulnerables(v: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_061_000 picoseconds. - Weight::from_parts(3_618_535, 0) - // Standard Error: 44 - .saturating_add(Weight::from_parts(10_774, 0).saturating_mul(v.into())) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking SlashingSpans (r:1 w:1) - /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:0 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: Staking SpanSlash (r:0 w:100) - /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) - /// The range of component `s` is `[0, 100]`. - fn force_unstake(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `2018 + s * (4 ±0)` - // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 87_914_000 picoseconds. - Weight::from_parts(95_688_129, 6248) - // Standard Error: 5_030 - .saturating_add(Weight::from_parts(1_487_249, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(12_u64)) - .saturating_add(T::DbWeight::get().writes(12_u64)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) - .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) - } - /// Storage: Staking UnappliedSlashes (r:1 w:1) - /// Proof Skipped: Staking UnappliedSlashes (max_values: None, max_size: None, mode: Measured) - /// The range of component `s` is `[1, 1000]`. - fn cancel_deferred_slash(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `66639` - // Estimated: `70104` - // Minimum execution time: 99_269_000 picoseconds. - Weight::from_parts(1_154_264_637, 70104) - // Standard Error: 76_592 - .saturating_add(Weight::from_parts(6_490_888, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ErasValidatorReward (r:1 w:0) - /// Proof: Staking ErasValidatorReward (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:257 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking ErasStakersClipped (r:1 w:0) - /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasRewardPoints (r:1 w:0) - /// Proof Skipped: Staking ErasRewardPoints (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasValidatorPrefs (r:1 w:0) - /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:257 w:0) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: System Account (r:257 w:257) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// The range of component `n` is `[0, 256]`. - fn payout_stakers_dead_controller(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `20217 + n * (143 ±0)` - // Estimated: `19844 + n * (2603 ±1)` - // Minimum execution time: 91_767_000 picoseconds. - Weight::from_parts(146_781_264, 19844) - // Standard Error: 31_341 - .saturating_add(Weight::from_parts(30_553_008, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(9_u64)) - .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2603).saturating_mul(n.into())) - } - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ErasValidatorReward (r:1 w:0) - /// Proof: Staking ErasValidatorReward (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:257 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:257 w:257) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking ErasStakersClipped (r:1 w:0) - /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasRewardPoints (r:1 w:0) - /// Proof Skipped: Staking ErasRewardPoints (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasValidatorPrefs (r:1 w:0) - /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:257 w:0) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: System Account (r:257 w:257) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:257 w:257) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:257 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// The range of component `n` is `[0, 256]`. - fn payout_stakers_alive_staked(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `33190 + n * (377 ±0)` - // Estimated: `30845 + n * (3774 ±0)` - // Minimum execution time: 121_303_000 picoseconds. - Weight::from_parts(151_046_907, 30845) - // Standard Error: 41_899 - .saturating_add(Weight::from_parts(49_837_804, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(11_u64)) - .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(3_u64)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into())) - } - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:3 w:3) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:2 w:2) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// The range of component `l` is `[1, 32]`. - fn rebond(l: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `2029 + l * (7 ±0)` - // Estimated: `8877` - // Minimum execution time: 90_068_000 picoseconds. - Weight::from_parts(93_137_456, 8877) - // Standard Error: 4_799 - .saturating_add(Weight::from_parts(54_421, 0).saturating_mul(l.into())) - .saturating_add(T::DbWeight::get().reads(9_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) - } - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking SlashingSpans (r:1 w:1) - /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: Staking SpanSlash (r:0 w:100) - /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) - /// The range of component `s` is `[1, 100]`. - fn reap_stash(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `2294 + s * (4 ±0)` - // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 103_139_000 picoseconds. - Weight::from_parts(107_036_296, 6248) - // Standard Error: 3_935 - .saturating_add(Weight::from_parts(1_465_860, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(12_u64)) - .saturating_add(T::DbWeight::get().writes(11_u64)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) - .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) - } - /// Storage: VoterList CounterForListNodes (r:1 w:0) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:200 w:0) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:110 w:0) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:110 w:0) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:11 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:110 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:110 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking CounterForValidators (r:1 w:0) - /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ValidatorCount (r:1 w:0) - /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinimumValidatorCount (r:1 w:0) - /// Proof: Staking MinimumValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:1) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ErasStakersClipped (r:0 w:10) - /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasValidatorPrefs (r:0 w:10) - /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) - /// Storage: Staking ErasStakers (r:0 w:10) - /// Proof Skipped: Staking ErasStakers (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasTotalStake (r:0 w:1) - /// Proof: Staking ErasTotalStake (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) - /// Storage: Staking ErasStartSessionIndex (r:0 w:1) - /// Proof: Staking ErasStartSessionIndex (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) - /// Storage: Staking MinimumActiveStake (r:0 w:1) - /// Proof: Staking MinimumActiveStake (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// The range of component `v` is `[1, 10]`. - /// The range of component `n` is `[0, 100]`. - fn new_era(v: u32, n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` - // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 587_156_000 picoseconds. - Weight::from_parts(590_176_000, 512390) - // Standard Error: 2_008_420 - .saturating_add(Weight::from_parts(64_526_052, 0).saturating_mul(v.into())) - // Standard Error: 200_128 - .saturating_add(Weight::from_parts(18_070_222, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(206_u64)) - .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) - .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(4_u64)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(v.into()))) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) - } - /// Storage: VoterList CounterForListNodes (r:1 w:0) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:200 w:0) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2000 w:0) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:2000 w:0) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1000 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:2000 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:2000 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking MinimumActiveStake (r:0 w:1) - /// Proof: Staking MinimumActiveStake (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// The range of component `v` is `[500, 1000]`. - /// The range of component `n` is `[500, 1000]`. - fn get_npos_voters(v: u32, n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `3217 + n * (911 ±0) + v * (395 ±0)` - // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 34_399_721_000 picoseconds. - Weight::from_parts(34_605_803_000, 512390) - // Standard Error: 380_106 - .saturating_add(Weight::from_parts(5_426_220, 0).saturating_mul(v.into())) - // Standard Error: 380_106 - .saturating_add(Weight::from_parts(3_318_197, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(201_u64)) - .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) - .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) - } - /// Storage: Staking CounterForValidators (r:1 w:0) - /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1001 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// The range of component `v` is `[500, 1000]`. - fn get_npos_targets(v: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `983 + v * (50 ±0)` - // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_392_849_000 picoseconds. - Weight::from_parts(64_373_879, 3510) - // Standard Error: 8_995 - .saturating_add(Weight::from_parts(4_721_536, 0).saturating_mul(v.into())) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) - .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) - } - /// Storage: Staking MinCommission (r:0 w:1) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinValidatorBond (r:0 w:1) - /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking MaxValidatorsCount (r:0 w:1) - /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ChillThreshold (r:0 w:1) - /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: Staking MaxNominatorsCount (r:0 w:1) - /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:0 w:1) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - fn set_staking_configs_all_set() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 7_529_000 picoseconds. - Weight::from_parts(7_970_000, 0) - .saturating_add(T::DbWeight::get().writes(6_u64)) - } - /// Storage: Staking MinCommission (r:0 w:1) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinValidatorBond (r:0 w:1) - /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking MaxValidatorsCount (r:0 w:1) - /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ChillThreshold (r:0 w:1) - /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: Staking MaxNominatorsCount (r:0 w:1) - /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:0 w:1) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - fn set_staking_configs_all_remove() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 7_011_000 picoseconds. - Weight::from_parts(7_317_000, 0) - .saturating_add(T::DbWeight::get().writes(6_u64)) - } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking ChillThreshold (r:1 w:0) - /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: Staking MaxNominatorsCount (r:1 w:0) - /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:1 w:0) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn chill_other() -> Weight { - // Proof Size summary in bytes: - // Measured: `1871` - // Estimated: `6248` - // Minimum execution time: 75_982_000 picoseconds. - Weight::from_parts(77_412_000, 6248) - .saturating_add(T::DbWeight::get().reads(11_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) - } - /// Storage: Staking MinCommission (r:1 w:0) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1 w:1) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - fn force_apply_min_commission() -> Weight { - // Proof Size summary in bytes: - // Measured: `694` - // Estimated: `3510` - // Minimum execution time: 13_923_000 picoseconds. - Weight::from_parts(14_356_000, 3510) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: Staking MinCommission (r:0 w:1) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn set_min_commission() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_415_000 picoseconds. - Weight::from_parts(3_679_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - fn bond() -> Weight { - // Proof Size summary in bytes: - // Measured: `1047` - // Estimated: `4764` - // Minimum execution time: 53_983_000 picoseconds. - Weight::from_parts(55_296_000, 4764) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) - } - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:3 w:3) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:2 w:2) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - fn bond_extra() -> Weight { - // Proof Size summary in bytes: - // Measured: `2028` - // Estimated: `8877` - // Minimum execution time: 96_590_000 picoseconds. - Weight::from_parts(98_921_000, 8877) - .saturating_add(RocksDbWeight::get().reads(9_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) - } - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:0) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:1 w:0) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:3 w:3) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:2 w:2) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - fn unbond() -> Weight { - // Proof Size summary in bytes: - // Measured: `2233` - // Estimated: `8877` - // Minimum execution time: 99_901_000 picoseconds. - Weight::from_parts(102_919_000, 8877) - .saturating_add(RocksDbWeight::get().reads(12_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) - } - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// The range of component `s` is `[0, 100]`. - fn withdraw_unbonded_update(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1021` - // Estimated: `4764` - // Minimum execution time: 45_230_000 picoseconds. - Weight::from_parts(47_052_829, 4764) - // Standard Error: 1_044 - .saturating_add(Weight::from_parts(43_887, 0).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - } - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking SlashingSpans (r:1 w:1) - /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: Staking SpanSlash (r:0 w:100) - /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) - /// The range of component `s` is `[0, 100]`. - fn withdraw_unbonded_kill(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `2294 + s * (4 ±0)` - // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 97_534_000 picoseconds. - Weight::from_parts(104_772_163, 6248) - // Standard Error: 3_674 - .saturating_add(Weight::from_parts(1_470_124, 0).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(13_u64)) - .saturating_add(RocksDbWeight::get().writes(11_u64)) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) - .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) - } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking MinValidatorBond (r:1 w:0) - /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking MinCommission (r:1 w:0) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1 w:1) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking MaxValidatorsCount (r:1 w:0) - /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:0) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:1 w:1) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking CounterForValidators (r:1 w:1) - /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn validate() -> Weight { - // Proof Size summary in bytes: - // Measured: `1414` - // Estimated: `4556` - // Minimum execution time: 57_467_000 picoseconds. - Weight::from_parts(59_437_000, 4556) - .saturating_add(RocksDbWeight::get().reads(11_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) - } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:128 w:128) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// The range of component `k` is `[1, 128]`. - fn kick(k: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1285 + k * (569 ±0)` - // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 32_857_000 picoseconds. - Weight::from_parts(37_116_967, 4556) - // Standard Error: 9_522 - .saturating_add(Weight::from_parts(8_796_167, 0).saturating_mul(k.into())) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) - .saturating_add(Weight::from_parts(0, 3033).saturating_mul(k.into())) - } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:1 w:0) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking MaxNominatorsCount (r:1 w:0) - /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:17 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// The range of component `n` is `[1, 16]`. - fn nominate(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1908 + n * (102 ±0)` - // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 69_613_000 picoseconds. - Weight::from_parts(68_079_061, 6248) - // Standard Error: 18_554 - .saturating_add(Weight::from_parts(4_012_761, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(12_u64)) - .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(6_u64)) - .saturating_add(Weight::from_parts(0, 2520).saturating_mul(n.into())) - } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn chill() -> Weight { - // Proof Size summary in bytes: - // Measured: `1748` - // Estimated: `6248` - // Minimum execution time: 60_430_000 picoseconds. - Weight::from_parts(62_702_000, 6248) - .saturating_add(RocksDbWeight::get().reads(8_u64)) - .saturating_add(RocksDbWeight::get().writes(6_u64)) - } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - fn set_payee() -> Weight { - // Proof Size summary in bytes: - // Measured: `808` - // Estimated: `4556` - // Minimum execution time: 14_276_000 picoseconds. - Weight::from_parts(14_766_000, 4556) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:2 w:2) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - fn set_controller() -> Weight { - // Proof Size summary in bytes: - // Measured: `907` - // Estimated: `8122` - // Minimum execution time: 21_710_000 picoseconds. - Weight::from_parts(22_430_000, 8122) - .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } - /// Storage: Staking ValidatorCount (r:0 w:1) - /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn set_validator_count() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_970_000 picoseconds. - Weight::from_parts(3_120_000, 0) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: Staking ForceEra (r:0 w:1) - /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - fn force_no_eras() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 9_362_000 picoseconds. - Weight::from_parts(9_785_000, 0) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: Staking ForceEra (r:0 w:1) - /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - fn force_new_era() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 9_275_000 picoseconds. - Weight::from_parts(9_678_000, 0) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: Staking ForceEra (r:0 w:1) - /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - fn force_new_era_always() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 9_414_000 picoseconds. - Weight::from_parts(9_848_000, 0) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: Staking Invulnerables (r:0 w:1) - /// Proof Skipped: Staking Invulnerables (max_values: Some(1), max_size: None, mode: Measured) - /// The range of component `v` is `[0, 1000]`. - fn set_invulnerables(v: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_061_000 picoseconds. - Weight::from_parts(3_618_535, 0) - // Standard Error: 44 - .saturating_add(Weight::from_parts(10_774, 0).saturating_mul(v.into())) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking SlashingSpans (r:1 w:1) - /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:0 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: Staking SpanSlash (r:0 w:100) - /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) - /// The range of component `s` is `[0, 100]`. - fn force_unstake(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `2018 + s * (4 ±0)` - // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 87_914_000 picoseconds. - Weight::from_parts(95_688_129, 6248) - // Standard Error: 5_030 - .saturating_add(Weight::from_parts(1_487_249, 0).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(12_u64)) - .saturating_add(RocksDbWeight::get().writes(12_u64)) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) - .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) - } - /// Storage: Staking UnappliedSlashes (r:1 w:1) - /// Proof Skipped: Staking UnappliedSlashes (max_values: None, max_size: None, mode: Measured) - /// The range of component `s` is `[1, 1000]`. - fn cancel_deferred_slash(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `66639` - // Estimated: `70104` - // Minimum execution time: 99_269_000 picoseconds. - Weight::from_parts(1_154_264_637, 70104) - // Standard Error: 76_592 - .saturating_add(Weight::from_parts(6_490_888, 0).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ErasValidatorReward (r:1 w:0) - /// Proof: Staking ErasValidatorReward (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:257 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking ErasStakersClipped (r:1 w:0) - /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasRewardPoints (r:1 w:0) - /// Proof Skipped: Staking ErasRewardPoints (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasValidatorPrefs (r:1 w:0) - /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:257 w:0) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: System Account (r:257 w:257) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// The range of component `n` is `[0, 256]`. - fn payout_stakers_dead_controller(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `20217 + n * (143 ±0)` - // Estimated: `19844 + n * (2603 ±1)` - // Minimum execution time: 91_767_000 picoseconds. - Weight::from_parts(146_781_264, 19844) - // Standard Error: 31_341 - .saturating_add(Weight::from_parts(30_553_008, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(9_u64)) - .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2603).saturating_mul(n.into())) - } - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ErasValidatorReward (r:1 w:0) - /// Proof: Staking ErasValidatorReward (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:257 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:257 w:257) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking ErasStakersClipped (r:1 w:0) - /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasRewardPoints (r:1 w:0) - /// Proof Skipped: Staking ErasRewardPoints (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasValidatorPrefs (r:1 w:0) - /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:257 w:0) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: System Account (r:257 w:257) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:257 w:257) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:257 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// The range of component `n` is `[0, 256]`. - fn payout_stakers_alive_staked(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `33190 + n * (377 ±0)` - // Estimated: `30845 + n * (3774 ±0)` - // Minimum execution time: 121_303_000 picoseconds. - Weight::from_parts(151_046_907, 30845) - // Standard Error: 41_899 - .saturating_add(Weight::from_parts(49_837_804, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(11_u64)) - .saturating_add(RocksDbWeight::get().reads((6_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into())) - } - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:3 w:3) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:2 w:2) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// The range of component `l` is `[1, 32]`. - fn rebond(l: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `2029 + l * (7 ±0)` - // Estimated: `8877` - // Minimum execution time: 90_068_000 picoseconds. - Weight::from_parts(93_137_456, 8877) - // Standard Error: 4_799 - .saturating_add(Weight::from_parts(54_421, 0).saturating_mul(l.into())) - .saturating_add(RocksDbWeight::get().reads(9_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) - } - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking SlashingSpans (r:1 w:1) - /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: Staking SpanSlash (r:0 w:100) - /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) - /// The range of component `s` is `[1, 100]`. - fn reap_stash(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `2294 + s * (4 ±0)` - // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 103_139_000 picoseconds. - Weight::from_parts(107_036_296, 6248) - // Standard Error: 3_935 - .saturating_add(Weight::from_parts(1_465_860, 0).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(12_u64)) - .saturating_add(RocksDbWeight::get().writes(11_u64)) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) - .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) - } - /// Storage: VoterList CounterForListNodes (r:1 w:0) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:200 w:0) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:110 w:0) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:110 w:0) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:11 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:110 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:110 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking CounterForValidators (r:1 w:0) - /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ValidatorCount (r:1 w:0) - /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinimumValidatorCount (r:1 w:0) - /// Proof: Staking MinimumValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:1) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ErasStakersClipped (r:0 w:10) - /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasValidatorPrefs (r:0 w:10) - /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) - /// Storage: Staking ErasStakers (r:0 w:10) - /// Proof Skipped: Staking ErasStakers (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasTotalStake (r:0 w:1) - /// Proof: Staking ErasTotalStake (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) - /// Storage: Staking ErasStartSessionIndex (r:0 w:1) - /// Proof: Staking ErasStartSessionIndex (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) - /// Storage: Staking MinimumActiveStake (r:0 w:1) - /// Proof: Staking MinimumActiveStake (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// The range of component `v` is `[1, 10]`. - /// The range of component `n` is `[0, 100]`. - fn new_era(v: u32, n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` - // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 587_156_000 picoseconds. - Weight::from_parts(590_176_000, 512390) - // Standard Error: 2_008_420 - .saturating_add(Weight::from_parts(64_526_052, 0).saturating_mul(v.into())) - // Standard Error: 200_128 - .saturating_add(Weight::from_parts(18_070_222, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(206_u64)) - .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) - .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(4_u64)) - .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(v.into()))) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) - } - /// Storage: VoterList CounterForListNodes (r:1 w:0) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:200 w:0) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2000 w:0) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:2000 w:0) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1000 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:2000 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:2000 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking MinimumActiveStake (r:0 w:1) - /// Proof: Staking MinimumActiveStake (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// The range of component `v` is `[500, 1000]`. - /// The range of component `n` is `[500, 1000]`. - fn get_npos_voters(v: u32, n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `3217 + n * (911 ±0) + v * (395 ±0)` - // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 34_399_721_000 picoseconds. - Weight::from_parts(34_605_803_000, 512390) - // Standard Error: 380_106 - .saturating_add(Weight::from_parts(5_426_220, 0).saturating_mul(v.into())) - // Standard Error: 380_106 - .saturating_add(Weight::from_parts(3_318_197, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(201_u64)) - .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) - .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) - } - /// Storage: Staking CounterForValidators (r:1 w:0) - /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1001 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// The range of component `v` is `[500, 1000]`. - fn get_npos_targets(v: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `983 + v * (50 ±0)` - // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_392_849_000 picoseconds. - Weight::from_parts(64_373_879, 3510) - // Standard Error: 8_995 - .saturating_add(Weight::from_parts(4_721_536, 0).saturating_mul(v.into())) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into()))) - .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) - } - /// Storage: Staking MinCommission (r:0 w:1) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinValidatorBond (r:0 w:1) - /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking MaxValidatorsCount (r:0 w:1) - /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ChillThreshold (r:0 w:1) - /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: Staking MaxNominatorsCount (r:0 w:1) - /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:0 w:1) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - fn set_staking_configs_all_set() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 7_529_000 picoseconds. - Weight::from_parts(7_970_000, 0) - .saturating_add(RocksDbWeight::get().writes(6_u64)) - } - /// Storage: Staking MinCommission (r:0 w:1) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinValidatorBond (r:0 w:1) - /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking MaxValidatorsCount (r:0 w:1) - /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ChillThreshold (r:0 w:1) - /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: Staking MaxNominatorsCount (r:0 w:1) - /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:0 w:1) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - fn set_staking_configs_all_remove() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 7_011_000 picoseconds. - Weight::from_parts(7_317_000, 0) - .saturating_add(RocksDbWeight::get().writes(6_u64)) - } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking ChillThreshold (r:1 w:0) - /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: Staking MaxNominatorsCount (r:1 w:0) - /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:1 w:0) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn chill_other() -> Weight { - // Proof Size summary in bytes: - // Measured: `1871` - // Estimated: `6248` - // Minimum execution time: 75_982_000 picoseconds. - Weight::from_parts(77_412_000, 6248) - .saturating_add(RocksDbWeight::get().reads(11_u64)) - .saturating_add(RocksDbWeight::get().writes(6_u64)) - } - /// Storage: Staking MinCommission (r:1 w:0) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1 w:1) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - fn force_apply_min_commission() -> Weight { - // Proof Size summary in bytes: - // Measured: `694` - // Estimated: `3510` - // Minimum execution time: 13_923_000 picoseconds. - Weight::from_parts(14_356_000, 3510) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: Staking MinCommission (r:0 w:1) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn set_min_commission() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_415_000 picoseconds. - Weight::from_parts(3_679_000, 0) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } -} diff --git a/runtime/cere-dev/Cargo.toml b/runtime/cere-dev/Cargo.toml index 3786d42f6..1d20d2b22 100644 --- a/runtime/cere-dev/Cargo.toml +++ b/runtime/cere-dev/Cargo.toml @@ -52,13 +52,16 @@ pallet-insecure-randomness-collective-flip = { workspace = true } pallet-membership = { workspace = true } pallet-multisig = { workspace = true } pallet-nomination-pools = { workspace = true } +pallet-nomination-pools-benchmarking = { workspace = true } pallet-nomination-pools-runtime-api = { workspace = true } pallet-offences = { workspace = true } +pallet-offences-benchmarking = { workspace = true, optional = true } pallet-preimage = { workspace = true } pallet-proxy = { workspace = true } pallet-recovery = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true, features = ["historical"] } +pallet-session-benchmarking = { workspace = true, optional = true } pallet-staking = { workspace = true } pallet-staking-reward-curve = { workspace = true } pallet-sudo = { workspace = true } @@ -201,9 +204,12 @@ runtime-benchmarks = [ "pallet-membership/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-nomination-pools/runtime-benchmarks", + "pallet-nomination-pools-benchmarking/runtime-benchmarks", + "pallet-offences-benchmarking/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", + "pallet-session-benchmarking/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-ddc-customers/runtime-benchmarks", "pallet-ddc-clusters/runtime-benchmarks", diff --git a/runtime/cere-dev/src/lib.rs b/runtime/cere-dev/src/lib.rs index 9c1645352..c58fa307f 100644 --- a/runtime/cere-dev/src/lib.rs +++ b/runtime/cere-dev/src/lib.rs @@ -28,12 +28,12 @@ use frame_election_provider_support::{onchain, BalancingConfig, SequentialPhragm use frame_support::{ construct_runtime, dispatch::DispatchClass, - pallet_prelude::{Get, StorageVersion}, + pallet_prelude::Get, parameter_types, traits::{ ConstBool, ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse, EqualPrivilegeOnly, - Everything, GetStorageVersion, Imbalance, InstanceFilter, KeyOwnerProofSystem, - LockIdentifier, Nothing, OnRuntimeUpgrade, OnUnbalanced, WithdrawReasons, + Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem, LockIdentifier, Nothing, + OnUnbalanced, WithdrawReasons, }, weights::{ constants::{ @@ -1434,13 +1434,6 @@ pub type SignedExtra = ( pallet_transaction_payment::ChargeTransactionPayment, ); -pub struct StakingMigrationV11OldPallet; -impl Get<&'static str> for StakingMigrationV11OldPallet { - fn get() -> &'static str { - "BagsList" - } -} - /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; @@ -1449,38 +1442,8 @@ pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; -// We don't need to run pallet_balances::pallets::MigrateToTrackInactive or -// pallet_balances::pallets::MigrateManyToTrackInactive since XCM related only. -// MigrateToTrackInactive and MigrateManyToTrackInactive simply add CheckingAccount value to -// InactiveIssuance so it's safe to skip it. -pub struct SetBalancesStorageVersions; -impl OnRuntimeUpgrade for SetBalancesStorageVersions { - fn on_runtime_upgrade() -> Weight { - let storage_version = ::on_chain_storage_version(); - if storage_version < 1 { - StorageVersion::new(1).put::(); - } - - RocksDbWeight::get().reads_writes(1, 1) - } -} -parameter_types! { - pub const SocietyPalletName: &'static str = "Society"; -} - /// Runtime migrations -type Migrations = ( - pallet_staking::migrations::v9::InjectValidatorsIntoVoterList, - pallet_staking::migrations::v10::MigrateToV10, - pallet_staking::migrations::v11::MigrateToV11, - pallet_staking::migrations::v12::MigrateToV12, - pallet_staking::migrations::v13::MigrateToV13, - frame_support::migrations::RemovePallet< - SocietyPalletName, - ::DbWeight, - >, - SetBalancesStorageVersions, -); +type Migrations = (); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< @@ -1536,9 +1499,12 @@ mod benches { [pallet_indices, Indices] [pallet_membership, TechnicalMembership] [pallet_multisig, Multisig] + [pallet_nomination_pools, NominationPoolsBench::] + [pallet_offences, OffencesBench::] [pallet_proxy, Proxy] [pallet_preimage, Preimage] [pallet_scheduler, Scheduler] + [pallet_session, SessionBench::] [pallet_staking, Staking] [pallet_ddc_customers, DdcCustomers] [pallet_ddc_clusters, DdcClusters] @@ -1878,7 +1844,10 @@ impl_runtime_apis! { // Trying to add benchmarks directly to the Session Pallet caused cyclic dependency // issues. To get around that, we separated the Session benchmarks into its own crate, // which is why we need these two lines below. + use pallet_session_benchmarking::Pallet as SessionBench; + use pallet_offences_benchmarking::Pallet as OffencesBench; use pallet_election_provider_support_benchmarking::Pallet as EPSBench; + use pallet_nomination_pools_benchmarking::Pallet as NominationPoolsBench; use frame_system_benchmarking::Pallet as SystemBench; use baseline::Pallet as BaselineBench; @@ -1898,13 +1867,19 @@ impl_runtime_apis! { // Trying to add benchmarks directly to the Session Pallet caused cyclic dependency // issues. To get around that, we separated the Session benchmarks into its own crate, // which is why we need these two lines below. + use pallet_session_benchmarking::Pallet as SessionBench; + use pallet_offences_benchmarking::Pallet as OffencesBench; use pallet_election_provider_support_benchmarking::Pallet as EPSBench; + use pallet_nomination_pools_benchmarking::Pallet as NominationPoolsBench; use frame_system_benchmarking::Pallet as SystemBench; use baseline::Pallet as BaselineBench; + impl pallet_session_benchmarking::Config for Runtime {} + impl pallet_offences_benchmarking::Config for Runtime {} impl pallet_election_provider_support_benchmarking::Config for Runtime {} impl frame_system_benchmarking::Config for Runtime {} impl baseline::Config for Runtime {} + impl pallet_nomination_pools_benchmarking::Config for Runtime {} let whitelist: Vec = vec![ // Block Number diff --git a/runtime/cere/Cargo.toml b/runtime/cere/Cargo.toml index a2208853c..8adf7f089 100644 --- a/runtime/cere/Cargo.toml +++ b/runtime/cere/Cargo.toml @@ -52,13 +52,16 @@ pallet-insecure-randomness-collective-flip = { workspace = true } pallet-membership = { workspace = true } pallet-multisig = { workspace = true } pallet-nomination-pools = { workspace = true } +pallet-nomination-pools-benchmarking = { workspace = true } pallet-nomination-pools-runtime-api = { workspace = true } pallet-offences = { workspace = true } +pallet-offences-benchmarking = { workspace = true, optional = true } pallet-preimage = { workspace = true } pallet-proxy = { workspace = true } pallet-recovery = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true, features = ["historical"] } +pallet-session-benchmarking = { workspace = true, optional = true } pallet-staking = { workspace = true } pallet-staking-reward-curve = { workspace = true } pallet-sudo = { workspace = true } @@ -204,10 +207,13 @@ runtime-benchmarks = [ "pallet-membership/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-nomination-pools/runtime-benchmarks", + "pallet-nomination-pools-benchmarking/runtime-benchmarks", + "pallet-offences-benchmarking/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", + "pallet-session-benchmarking/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-tips/runtime-benchmarks", diff --git a/runtime/cere/src/lib.rs b/runtime/cere/src/lib.rs index 0ab9c4042..fff14b74a 100644 --- a/runtime/cere/src/lib.rs +++ b/runtime/cere/src/lib.rs @@ -28,12 +28,12 @@ use frame_election_provider_support::{onchain, BalancingConfig, SequentialPhragm use frame_support::{ construct_runtime, dispatch::DispatchClass, - pallet_prelude::{Get, StorageVersion}, + pallet_prelude::Get, parameter_types, traits::{ ConstBool, ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse, EqualPrivilegeOnly, - Everything, GetStorageVersion, Imbalance, InstanceFilter, KeyOwnerProofSystem, - LockIdentifier, Nothing, OnRuntimeUpgrade, OnUnbalanced, WithdrawReasons, + Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem, LockIdentifier, Nothing, + OnUnbalanced, WithdrawReasons, }, weights::{ constants::{ @@ -1439,29 +1439,6 @@ pub type SignedExtra = ( pallet_transaction_payment::ChargeTransactionPayment, ); -pub struct StakingMigrationV11OldPallet; -impl Get<&'static str> for StakingMigrationV11OldPallet { - fn get() -> &'static str { - "BagsList" - } -} - -// We don't need to run pallet_balances::pallets::MigrateToTrackInactive or -// pallet_balances::pallets::MigrateManyToTrackInactive since XCM related only. -// MigrateToTrackInactive and MigrateManyToTrackInactive simply add CheckingAccount value to -// InactiveIssuance so it's safe to skip it. -pub struct SetBalancesStorageVersions; -impl OnRuntimeUpgrade for SetBalancesStorageVersions { - fn on_runtime_upgrade() -> Weight { - let storage_version = ::on_chain_storage_version(); - if storage_version < 1 { - StorageVersion::new(1).put::(); - } - - RocksDbWeight::get().reads_writes(1, 1) - } -} - /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; @@ -1470,23 +1447,8 @@ pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; -parameter_types! { - pub const SocietyPalletName: &'static str = "Society"; -} - /// Runtime migrations -type Migrations = ( - pallet_staking::migrations::v9::InjectValidatorsIntoVoterList, - pallet_staking::migrations::v10::MigrateToV10, - pallet_staking::migrations::v11::MigrateToV11, - pallet_staking::migrations::v12::MigrateToV12, - pallet_staking::migrations::v13::MigrateToV13, - frame_support::migrations::RemovePallet< - SocietyPalletName, - ::DbWeight, - >, - SetBalancesStorageVersions, -); +type Migrations = (); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< @@ -1534,9 +1496,12 @@ mod benches { [pallet_indices, Indices] [pallet_membership, TechnicalMembership] [pallet_multisig, Multisig] + [pallet_nomination_pools, NominationPoolsBench::] + [pallet_offences, OffencesBench::] [pallet_proxy, Proxy] [pallet_preimage, Preimage] [pallet_scheduler, Scheduler] + [pallet_session, SessionBench::] [pallet_staking, Staking] [frame_system, SystemBench::] [pallet_timestamp, Timestamp] @@ -1870,7 +1835,10 @@ impl_runtime_apis! { // Trying to add benchmarks directly to the Session Pallet caused cyclic dependency // issues. To get around that, we separated the Session benchmarks into its own crate, // which is why we need these two lines below. + use pallet_session_benchmarking::Pallet as SessionBench; + use pallet_offences_benchmarking::Pallet as OffencesBench; use pallet_election_provider_support_benchmarking::Pallet as EPSBench; + use pallet_nomination_pools_benchmarking::Pallet as NominationPoolsBench; use frame_system_benchmarking::Pallet as SystemBench; use baseline::Pallet as BaselineBench; @@ -1890,13 +1858,19 @@ impl_runtime_apis! { // Trying to add benchmarks directly to the Session Pallet caused cyclic dependency // issues. To get around that, we separated the Session benchmarks into its own crate, // which is why we need these two lines below. + use pallet_session_benchmarking::Pallet as SessionBench; + use pallet_offences_benchmarking::Pallet as OffencesBench; use pallet_election_provider_support_benchmarking::Pallet as EPSBench; + use pallet_nomination_pools_benchmarking::Pallet as NominationPoolsBench; use frame_system_benchmarking::Pallet as SystemBench; use baseline::Pallet as BaselineBench; + impl pallet_session_benchmarking::Config for Runtime {} + impl pallet_offences_benchmarking::Config for Runtime {} impl pallet_election_provider_support_benchmarking::Config for Runtime {} impl frame_system_benchmarking::Config for Runtime {} impl baseline::Config for Runtime {} + impl pallet_nomination_pools_benchmarking::Config for Runtime {} let whitelist: Vec = vec![ // Block Number diff --git a/shell.nix b/shell.nix index 98ec71450..11b077422 100644 --- a/shell.nix +++ b/shell.nix @@ -13,7 +13,7 @@ let rev = "1fe6ed37fd9beb92afe90671c0c2a662a03463dd"; }; nixpkgs = import pinned { overlays = [ mozillaOverlay ]; }; - toolchain = with nixpkgs; (rustChannelOf { date = "2023-05-23"; channel = "nightly"; }); + toolchain = with nixpkgs; (rustChannelOf { date = "2023-10-05"; channel = "nightly"; }); rust-wasm = toolchain.rust.override { targets = [ "wasm32-unknown-unknown" ]; extensions = ["rust-src"];