From 656e5410ba375075e2d611ddb0746a8f25f9988a Mon Sep 17 00:00:00 2001 From: Maksim Ramanenkau Date: Sun, 14 Apr 2024 15:01:34 +0400 Subject: [PATCH] Fix/staking migrations (#300) ### Description This PR applies missing staking migrations with some changes in v9 and v11: 1. The migration to v9 has been altered to set the next pallet version only. The remaining parts are not needed because we are already using VoterList and it's in good condition. To change the migration, the source code has been moved from workplace dependencies to the Cere repo. 2. In migration to v11, there is no need to move data from BagsList to VoterList since we are already using VoterList. Instead, BagsList should be removed. 3. Benchmarking and Clippy checks are disabled for the sake of time. 4. Apply migrations to v8, v10, v12, v13. Notes: 1. BagsList has been migrated to VoterList in [v4.1.0](https://github.com/Cerebellum-Network/blockchain-node/compare/v4.0.0..v4.1.0#diff-8979c26ba42edc867488ec32089e6e48df0774444fe9a4726a09d129545060dbR1347) but the migration was not written and executed properly. 2. Staking pallet changes can be reviewed with `diff -r substrate/frame/staking blockchain-node/pallets/staking` or with [kdiff3](https://kdiff3.sourceforge.net/) if one prefers graphical UI. In both cases, you must have [Substrate](https://github.com/paritytech/substrate) locally. 3. To get back to the original staking pallet, [b80f635](https://github.com/Cerebellum-Network/blockchain-node/pull/300/commits/b80f635cd97808e408184bfdddb736e3aa1aa5ab), [5c8fcbd](https://github.com/Cerebellum-Network/blockchain-node/pull/300/commits/5c8fcbd257cd8e1a8d3213b02cdab0cd05e16a6b), and [1b71aa8](https://github.com/Cerebellum-Network/blockchain-node/pull/300/commits/1b71aa85175f0e196c7dc5cad8e899eaa7f8322b) should be reverted ### Types of Changes - [ ] Tech Debt (Code improvements) - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Dependency upgrade (A change in substrate or any 3rd party crate version) ### Migrations and Hooks - [ ] This change requires a runtime migration. - [ ] Modifies `on_initialize` - [ ] Modifies `on_finalize` ### Checklist - [x] Change has been tested locally. - [ ] Change adds / updates tests. - [ ] Changelog doc updated. --- .github/workflows/ci.yaml | 59 +- CHANGELOG.md | 4 +- Cargo.lock | 984 ++--- Cargo.toml | 5 +- pallets/staking/Cargo.toml | 73 + pallets/staking/README.md | 261 ++ pallets/staking/src/benchmarking.rs | 1066 +++++ pallets/staking/src/inflation.rs | 108 + pallets/staking/src/lib.rs | 958 +++++ pallets/staking/src/migrations.rs | 503 +++ pallets/staking/src/mock.rs | 808 ++++ pallets/staking/src/pallet/impls.rs | 1838 ++++++++ pallets/staking/src/pallet/mod.rs | 1781 ++++++++ pallets/staking/src/slashing.rs | 861 ++++ pallets/staking/src/testing_utils.rs | 240 ++ pallets/staking/src/tests.rs | 5847 ++++++++++++++++++++++++++ pallets/staking/src/weights.rs | 1506 +++++++ runtime/cere-dev/Cargo.toml | 6 - runtime/cere-dev/src/lib.rs | 40 +- runtime/cere/Cargo.toml | 6 - runtime/cere/src/lib.rs | 40 +- rust-toolchain.toml | 2 +- scripts/init.sh | 4 +- 23 files changed, 16427 insertions(+), 573 deletions(-) create mode 100644 pallets/staking/Cargo.toml create mode 100644 pallets/staking/README.md create mode 100644 pallets/staking/src/benchmarking.rs create mode 100644 pallets/staking/src/inflation.rs create mode 100644 pallets/staking/src/lib.rs create mode 100644 pallets/staking/src/migrations.rs create mode 100644 pallets/staking/src/mock.rs create mode 100644 pallets/staking/src/pallet/impls.rs create mode 100644 pallets/staking/src/pallet/mod.rs create mode 100644 pallets/staking/src/slashing.rs create mode 100644 pallets/staking/src/testing_utils.rs create mode 100644 pallets/staking/src/tests.rs create mode 100644 pallets/staking/src/weights.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 86141edd2..acca7e811 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: - name: Install nightly toolchain uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2023-05-23 + toolchain: nightly-2024-03-12 override: true components: rustfmt - name: Check TOML @@ -44,7 +44,7 @@ jobs: - name: Install nightly toolchain uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2023-05-23 + toolchain: nightly-2024-03-12 override: true target: wasm32-unknown-unknown - name: Rust Cache @@ -55,35 +55,38 @@ jobs: - name: Run dev chain run: | timeout --preserve-status 30s ./target/release/cere --dev - - name: Check Build for Benchmarking - run: > - pushd node && - cargo check --features=runtime-benchmarks --release +# 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 Try-Runtime run: | cargo check --features=try-runtime --release - - 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 +# 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 tests: name: Run tests @@ -98,7 +101,7 @@ jobs: - name: Install nightly toolchain uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2023-05-23 + toolchain: nightly-2024-03-12 override: true target: wasm32-unknown-unknown - name: Rust Cache diff --git a/CHANGELOG.md b/CHANGELOG.md index ae19b51bb..eb8a68df2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [D] Changes is `Cere Dev` Runtime ## [vNext] -- [C,D] `pallet-ddc-customers`: implemented bucket removal -- Added ChargeError event to payout pallet ### Added -- ... +- Missing storage migrations to Staking pallet ### Changed diff --git a/Cargo.lock b/Cargo.lock index aff636a9b..073e5a539 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,9 +48,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -73,32 +73,33 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.12", + "getrandom 0.2.14", "once_cell", "version_check", ] [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom 0.2.12", + "getrandom 0.2.14", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -129,9 +130,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -177,9 +178,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "approx" @@ -266,9 +267,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" dependencies = [ "async-lock 3.3.0", "cfg-if", @@ -277,7 +278,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.31", + "rustix 0.38.32", "slab", "tracing", "windows-sys 0.52.0", @@ -300,29 +301,29 @@ checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" dependencies = [ "event-listener 4.0.3", "event-listener-strategy", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.14", ] [[package]] name = "async-recursion" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -335,7 +336,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.14", ] [[package]] @@ -351,15 +352,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line 0.21.0", "cc", @@ -430,13 +431,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", - "prettyplease 0.2.16", + "prettyplease 0.2.17", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -447,9 +448,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bitvec" @@ -496,9 +497,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ "arrayref", "arrayvec 0.7.4", @@ -566,9 +567,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bstr" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "serde", @@ -585,9 +586,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byte-slice-cast" @@ -612,9 +613,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2490600f404f2b94c167e31d3ed1d5f3c225a0f3b80230053b3e0b7b962bd9" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "byteorder" @@ -624,9 +625,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "bzip2-sys" @@ -650,9 +651,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceed8ef69d8518a5dda55c07425450b58a4e1946f4951eab6d7191ee86c2443d" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" dependencies = [ "serde", ] @@ -665,7 +666,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "thiserror", @@ -673,9 +674,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" dependencies = [ "jobserver", "libc", @@ -778,16 +779,13 @@ 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-society", "pallet-staking", "pallet-staking-reward-curve", @@ -896,16 +894,13 @@ 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-society", "pallet-staking", "pallet-staking-reward-curve", @@ -1013,9 +1008,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.6" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6100bc57b6209840798d95cb2775684849d332f7bd788db2a8c8caf7ef82a41a" +checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d" dependencies = [ "smallvec", ] @@ -1058,16 +1053,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.33" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -1107,9 +1102,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.18" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -1117,9 +1112,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -1129,21 +1124,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "codespan-reporting" @@ -1163,12 +1158,12 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "comfy-table" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" +checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ - "strum 0.25.0", - "strum_macros 0.25.3", + "strum 0.26.2", + "strum_macros 0.26.2", "unicode-width", ] @@ -1208,9 +1203,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const-random" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" dependencies = [ "const-random-macro", ] @@ -1221,7 +1216,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.12", + "getrandom 0.2.14", "once_cell", "tiny-keccak", ] @@ -1375,9 +1370,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] @@ -1493,9 +1488,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", @@ -1516,14 +1511,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] name = "cxx" -version = "1.0.115" +version = "1.0.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de00f15a6fa069c99b88c5c78c4541d0e7899a33b86f7480e23df2431fce0bc" +checksum = "21db378d04296a84d8b7d047c36bb3954f0b46529db725d7e62fb02f9ba53ccc" dependencies = [ "cc", "cxxbridge-flags", @@ -1533,9 +1528,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.115" +version = "1.0.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a71e1e631fa2f2f5f92e8b0d860a00c198c6771623a6cefcc863e3554f0d8d6" +checksum = "3e5262a7fa3f0bae2a55b767c223ba98032d7c328f5c13fa5cdc980b77fc0658" dependencies = [ "cc", "codespan-reporting", @@ -1543,24 +1538,24 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] name = "cxxbridge-flags" -version = "1.0.115" +version = "1.0.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3fed61d56ba497c4efef9144dfdbaa25aa58f2f6b3a7cf441d4591c583745c" +checksum = "be8dcadd2e2fb4a501e1d9e93d6e88e6ea494306d8272069c92d5a9edf8855c0" [[package]] name = "cxxbridge-macro" -version = "1.0.115" +version = "1.0.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8908e380a8efd42150c017b0cfa31509fc49b6d47f7cb6b33e93ffb8f4e3661e" +checksum = "ad08a837629ad949b73d032c637653d069e909cffe4ee7870b02301939ce39cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -1605,9 +1600,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "zeroize", @@ -1743,7 +1738,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -1767,7 +1762,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.48", + "syn 2.0.58", "termcolor", "walkdir", ] @@ -1780,9 +1775,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "downcast-rs" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dtoa" @@ -1813,9 +1808,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecdsa" @@ -1864,11 +1859,11 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "ed25519 2.2.3", "rand_core 0.6.4", "serde", @@ -1893,9 +1888,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "elliptic-curve" @@ -1928,7 +1923,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.109", @@ -1936,22 +1931,22 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5998b4f30320c9d93aed72f63af821bfdac50465b75428fce77b48ec482c3939" +checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" +checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -2003,7 +1998,7 @@ checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" dependencies = [ "concurrent-queue", "parking", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.14", ] [[package]] @@ -2013,7 +2008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" dependencies = [ "event-listener 4.0.3", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.14", ] [[package]] @@ -2027,15 +2022,16 @@ dependencies = [ [[package]] name = "expander" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f86a749cf851891866c10515ef6c299b5c69661465e9c3bbe7e07a2b77fb0f7" +checksum = "00e83c02035136f1592a47964ea60c05a50e4ed8b5892cfac197063850898d4d" dependencies = [ "blake2", "fs-err", + "prettier-please", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -2052,9 +2048,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "fdlimit" @@ -2077,9 +2073,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" +checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" [[package]] name = "file-per-thread-logger" @@ -2267,7 +2263,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -2386,7 +2382,7 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -2398,7 +2394,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -2408,7 +2404,7 @@ source = "git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0#948 dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -2542,12 +2538,12 @@ checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "futures-core", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.14", ] [[package]] @@ -2558,7 +2554,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -2586,9 +2582,9 @@ checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" @@ -2603,7 +2599,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.14", "pin-utils", "slab", ] @@ -2660,22 +2656,31 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom_or_panic" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "ghash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", "polyval", ] @@ -2711,8 +2716,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.5", - "regex-syntax 0.8.2", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", ] [[package]] @@ -2728,9 +2733,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -2738,7 +2743,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.2", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -2780,7 +2785,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.7", + "ahash 0.7.8", ] [[package]] @@ -2789,7 +2794,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.11", ] [[package]] @@ -2804,6 +2809,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -2815,9 +2826,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.5" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -2893,9 +2904,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -2910,7 +2921,7 @@ checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.14", ] [[package]] @@ -2953,8 +2964,8 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite 0.2.13", - "socket2 0.5.5", + "pin-project-lite 0.2.14", + "socket2 0.5.6", "tokio", "tower-service", "tracing", @@ -3093,9 +3104,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.2" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -3109,9 +3120,9 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" [[package]] name = "indicatif" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" dependencies = [ "console", "instant", @@ -3159,7 +3170,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.5", + "hermit-abi 0.3.9", "libc", "windows-sys 0.48.0", ] @@ -3176,7 +3187,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.5", + "socket2 0.5.6", "widestring", "windows-sys 0.48.0", "winreg", @@ -3190,12 +3201,12 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi 0.3.5", - "rustix 0.38.31", + "hermit-abi 0.3.9", + "libc", "windows-sys 0.52.0", ] @@ -3210,24 +3221,24 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -3321,7 +3332,7 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44e8ab85614a08792b9bff6c8feee23be78c98d0182d4c622c05256ab553892a" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro-crate 1.1.3", "proc-macro2", "quote", @@ -3451,12 +3462,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.4", ] [[package]] @@ -3474,7 +3485,7 @@ dependencies = [ "bytes", "futures", "futures-timer", - "getrandom 0.2.12", + "getrandom 0.2.14", "instant", "libp2p-allow-block-list", "libp2p-connection-limits", @@ -3593,7 +3604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "276bb57e7af15d8f100d3c11cbdd32c6752b7eef4ba7a18ecf464972c07abcce" dependencies = [ "bs58", - "ed25519-dalek 2.1.0", + "ed25519-dalek 2.1.1", "log", "multiaddr", "multihash", @@ -3772,7 +3783,7 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fba456131824ab6acd4c7bf61e9c0f0a3014b5fc9868ccb8e10d344594cdc4f" dependencies = [ - "heck", + "heck 0.4.1", "quote", "syn 1.0.109", ] @@ -3860,13 +3871,12 @@ dependencies = [ [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", - "redox_syscall 0.4.1", ] [[package]] @@ -3934,9 +3944,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.15" +version = "1.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" +checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" dependencies = [ "cc", "pkg-config", @@ -4000,9 +4010,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "lru" @@ -4060,7 +4070,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -4074,7 +4084,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -4085,7 +4095,7 @@ checksum = "d710e1214dffbab3b5dacb21475dde7d6ed84c69ff722b3a47a782668d44fbac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -4096,7 +4106,7 @@ checksum = "b8fb85ec1620619edf2984a7693497d4ec88a9665d8b87e942856884c92dbf2a" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -4138,9 +4148,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memfd" @@ -4148,7 +4158,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.31", + "rustix 0.38.32", ] [[package]] @@ -4190,6 +4200,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -4207,9 +4229,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -4326,9 +4348,9 @@ dependencies = [ [[package]] name = "nalgebra" -version = "0.32.3" +version = "0.32.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307ed9b18cc2423f29e83f84fd23a8e73628727990181f18641a8b5dc2ab1caa" +checksum = "3ea4908d4f23254adda3daa60ffef0f1ac7b8c3e9a864cf3cc154b251908a2ef" dependencies = [ "approx", "matrixmultiply", @@ -4415,9 +4437,9 @@ dependencies = [ [[package]] name = "netlink-sys" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" +checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" dependencies = [ "bytes", "futures", @@ -4481,9 +4503,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" dependencies = [ "num-traits", ] @@ -4506,11 +4528,10 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] @@ -4528,9 +4549,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -4541,7 +4562,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.5", + "hermit-abi 0.3.9", "libc", ] @@ -4595,9 +4616,9 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl-probe" @@ -4812,7 +4833,7 @@ source = "git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0#948 dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -5199,26 +5220,6 @@ 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" @@ -5247,30 +5248,6 @@ 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" @@ -5356,22 +5333,6 @@ 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-society" version = "4.0.0-dev" @@ -5394,7 +5355,6 @@ dependencies = [ [[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", @@ -5402,16 +5362,24 @@ 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]] @@ -5422,7 +5390,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -5611,7 +5579,7 @@ version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" dependencies = [ - "proc-macro-crate 2.0.0", + "proc-macro-crate 2.0.2", "proc-macro2", "quote", "syn 1.0.109", @@ -5736,9 +5704,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.7" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546" +checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" dependencies = [ "memchr", "thiserror", @@ -5747,9 +5715,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.7" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809" +checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" dependencies = [ "pest", "pest_generator", @@ -5757,22 +5725,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.7" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e" +checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] name = "pest_meta" -version = "2.7.7" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a" +checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" dependencies = [ "once_cell", "pest", @@ -5786,27 +5754,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.2.2", + "indexmap 2.2.6", ] [[package]] name = "pin-project" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -5817,9 +5785,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -5839,26 +5807,27 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "platforms" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] name = "polling" -version = "3.4.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14" +checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" dependencies = [ "cfg-if", "concurrent-queue", - "pin-project-lite 0.2.13", - "rustix 0.38.31", + "hermit-abi 0.3.9", + "pin-project-lite 0.2.14", + "rustix 0.38.32", "tracing", "windows-sys 0.52.0", ] @@ -5870,19 +5839,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", "universal-hash", ] [[package]] name = "polyval" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", "universal-hash", ] @@ -5934,11 +5903,21 @@ dependencies = [ "termtree", ] +[[package]] +name = "prettier-please" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3" +dependencies = [ + "proc-macro2", + "syn 2.0.58", +] + [[package]] name = "prettyplease" -version = "0.1.25" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +checksum = "f28f53e8b192565862cf99343194579a022eb9c7dd3a8d03134734803c7b3125" dependencies = [ "proc-macro2", "syn 1.0.109", @@ -5946,12 +5925,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" dependencies = [ "proc-macro2", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -5979,11 +5958,12 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" dependencies = [ - "toml_edit 0.20.7", + "toml_datetime", + "toml_edit 0.20.2", ] [[package]] @@ -6018,14 +5998,14 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -6064,7 +6044,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -6084,13 +6064,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ "bytes", - "heck", + "heck 0.4.1", "itertools", "lazy_static", "log", "multimap", "petgraph", - "prettyplease 0.1.25", + "prettyplease 0.1.11", "prost", "prost-types", "regex", @@ -6261,7 +6241,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.12", + "getrandom 0.2.14", ] [[package]] @@ -6290,9 +6270,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.8.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -6340,11 +6320,11 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "getrandom 0.2.12", + "getrandom 0.2.14", "libredox", "thiserror", ] @@ -6366,7 +6346,7 @@ checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -6383,14 +6363,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.5", - "regex-syntax 0.8.2", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", ] [[package]] @@ -6404,13 +6384,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -6421,9 +6401,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "resolv-conf" @@ -6462,16 +6442,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", - "getrandom 0.2.12", + "cfg-if", + "getrandom 0.2.14", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -6553,7 +6534,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.21", + "semver 1.0.22", ] [[package]] @@ -6581,11 +6562,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys 0.4.13", @@ -6611,7 +6592,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", - "ring 0.17.7", + "ring 0.17.8", "rustls-webpki", "sct", ] @@ -6643,15 +6624,15 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "rw-stream-sink" @@ -6666,9 +6647,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "safe-mix" @@ -6801,7 +6782,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -6996,7 +6977,7 @@ name = "sc-consensus-grandpa" version = "0.10.0-dev" source = "git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0#948fbd2fd1233dc26dbb9f9bbc1d2cca2c03945d" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.11", "array-bytes", "async-trait", "dyn-clone", @@ -7239,7 +7220,7 @@ name = "sc-network-gossip" version = "0.10.0-dev" source = "git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0#948fbd2fd1233dc26dbb9f9bbc1d2cca2c03945d" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.11", "futures", "futures-timer", "libp2p", @@ -7628,7 +7609,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -7690,9 +7671,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.10.0" +version = "2.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" +checksum = "7c453e59a955f81fb62ee5d596b450383d699f152d350e9d23a0db2adb78e4c0" dependencies = [ "bitvec", "cfg-if", @@ -7704,9 +7685,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.10.0" +version = "2.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" +checksum = "18cf6c6447f813ef19eb450e985bcce6705f9ce7660db221b59093d15c79c4b7" dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", @@ -7729,7 +7710,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.11", "cfg-if", "hashbrown 0.13.2", ] @@ -7744,7 +7725,7 @@ dependencies = [ "arrayvec 0.5.2", "curve25519-dalek 2.1.3", "getrandom 0.1.16", - "merlin", + "merlin 2.0.1", "rand 0.7.3", "rand_core 0.5.1", "sha2 0.8.2", @@ -7752,6 +7733,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "schnorrkel" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" +dependencies = [ + "arrayref", + "arrayvec 0.7.4", + "curve25519-dalek 4.1.2", + "getrandom_or_panic", + "merlin 3.0.0", + "rand_core 0.6.4", + "sha2 0.10.8", + "subtle", + "zeroize", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -7770,7 +7768,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] @@ -7817,9 +7815,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -7830,9 +7828,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -7858,9 +7856,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" dependencies = [ "serde", ] @@ -7873,29 +7871,29 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -7921,7 +7919,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", ] [[package]] @@ -7946,7 +7944,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", ] [[package]] @@ -8046,9 +8044,9 @@ checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "snap" @@ -8065,9 +8063,9 @@ dependencies = [ "aes-gcm", "blake2", "chacha20poly1305", - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "rand_core 0.6.4", - "ring 0.17.7", + "ring 0.17.8", "rustc_version 0.4.0", "sha2 0.10.8", "subtle", @@ -8085,12 +8083,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -8142,7 +8140,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -8314,7 +8312,7 @@ dependencies = [ "lazy_static", "libsecp256k1", "log", - "merlin", + "merlin 2.0.1", "parity-scale-codec", "parking_lot 0.12.1", "paste", @@ -8322,7 +8320,7 @@ dependencies = [ "rand 0.8.5", "regex", "scale-info", - "schnorrkel", + "schnorrkel 0.9.1", "secp256k1", "secrecy", "serde", @@ -8360,7 +8358,7 @@ source = "git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0#948 dependencies = [ "quote", "sp-core-hashing", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -8379,7 +8377,7 @@ source = "git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0#948 dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -8568,7 +8566,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -8710,7 +8708,7 @@ name = "sp-trie" version = "22.0.0" source = "git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0#948fbd2fd1233dc26dbb9f9bbc1d2cca2c03945d" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.11", "hash-db", "hashbrown 0.13.2", "lazy_static", @@ -8753,7 +8751,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -8819,9 +8817,9 @@ dependencies = [ [[package]] name = "ss58-registry" -version = "1.46.0" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1114ee5900b8569bbc8b1a014a942f937b752af4b44f4607430b5f86cedaac0" +checksum = "4743ce898933fbff7bbf414f497c459a782d496269644b3d650a398ae6a487ba" dependencies = [ "Inflector", "num-format", @@ -8874,9 +8872,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" @@ -8889,9 +8887,9 @@ dependencies = [ [[package]] name = "strum" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" [[package]] name = "strum_macros" @@ -8899,7 +8897,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", @@ -8908,26 +8906,26 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.25.3" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] name = "substrate-bip39" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e620c7098893ba667438b47169c00aacdd9e7c10e042250ce2b60b087ec97328" +checksum = "6a7590dc041b9bc2825e52ce5af8416c73dbe9d0654402bfd4b4941938b94d8f" dependencies = [ "hmac 0.11.0", "pbkdf2 0.8.0", - "schnorrkel", + "schnorrkel 0.11.4", "sha2 0.9.9", "zeroize", ] @@ -9016,7 +9014,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -9056,9 +9054,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -9106,19 +9104,19 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.13" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "rustix 0.38.31", + "rustix 0.38.32", "windows-sys 0.52.0", ] @@ -9139,22 +9137,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -9165,9 +9163,9 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -9268,9 +9266,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -9278,9 +9276,9 @@ dependencies = [ "mio", "num_cpus", "parking_lot 0.12.1", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.14", "signal-hook-registry", - "socket2 0.5.5", + "socket2 0.5.6", "tokio-macros", "windows-sys 0.48.0", ] @@ -9293,7 +9291,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -9319,12 +9317,12 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.14", "tokio", "tokio-util", ] @@ -9339,7 +9337,7 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.14", "tokio", "tracing", ] @@ -9367,9 +9365,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] @@ -9380,7 +9378,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.2", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -9389,11 +9387,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.7" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.2.2", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -9415,14 +9413,14 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "bytes", "futures-core", "futures-util", "http", "http-body", "http-range-header", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.14", "tower-layer", "tower-service", ] @@ -9446,7 +9444,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.14", "tracing-attributes", "tracing-core", ] @@ -9459,7 +9457,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -9692,9 +9690,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -9794,9 +9792,9 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -9825,9 +9823,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -9835,24 +9833,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -9862,9 +9860,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9872,22 +9870,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-instrument" @@ -10210,9 +10208,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -10224,7 +10222,7 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] @@ -10252,7 +10250,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.31", + "rustix 0.38.32", ] [[package]] @@ -10267,9 +10265,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -10327,7 +10325,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -10354,7 +10352,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -10389,17 +10387,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -10416,9 +10414,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -10434,9 +10432,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -10452,9 +10450,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -10470,9 +10468,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -10488,9 +10486,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -10506,9 +10504,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -10524,15 +10522,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winnow" -version = "0.5.37" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cad8365489051ae9f054164e459304af2e7e9bb407c958076c8bf4aef52da5" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] @@ -10608,6 +10606,26 @@ dependencies = [ "time", ] +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "zeroize" version = "1.7.0" @@ -10625,7 +10643,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.58", ] [[package]] @@ -10668,9 +10686,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index b0ea7bca1..eb820d114 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "pallets/ddc-staking", "pallets/erc20", "pallets/erc721", + "pallets/staking", "primitives", "runtime/cere", "runtime/cere-dev", @@ -83,7 +84,6 @@ 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 } @@ -91,7 +91,8 @@ pallet-scheduler = { git = "https://github.com/paritytech/substrate.git", branch 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-society = { 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 = { 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-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/pallets/staking/Cargo.toml b/pallets/staking/Cargo.toml new file mode 100644 index 000000000..00c245367 --- /dev/null +++ b/pallets/staking/Cargo.toml @@ -0,0 +1,73 @@ +[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 new file mode 100644 index 000000000..ccb9901a6 --- /dev/null +++ b/pallets/staking/README.md @@ -0,0 +1,261 @@ +# 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 new file mode 100644 index 000000000..6ef782012 --- /dev/null +++ b/pallets/staking/src/benchmarking.rs @@ -0,0 +1,1066 @@ +// 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 new file mode 100644 index 000000000..8b4a85b6c --- /dev/null +++ b/pallets/staking/src/inflation.rs @@ -0,0 +1,108 @@ +// 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 new file mode 100644 index 000000000..57e4edcc5 --- /dev/null +++ b/pallets/staking/src/lib.rs @@ -0,0 +1,958 @@ +// 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 new file mode 100644 index 000000000..83a9be204 --- /dev/null +++ b/pallets/staking/src/migrations.rs @@ -0,0 +1,503 @@ +// 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 new file mode 100644 index 000000000..d9023c9a2 --- /dev/null +++ b/pallets/staking/src/mock.rs @@ -0,0 +1,808 @@ +// 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 new file mode 100644 index 000000000..fb5c27b15 --- /dev/null +++ b/pallets/staking/src/pallet/impls.rs @@ -0,0 +1,1838 @@ +// 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 new file mode 100644 index 000000000..46d5403ee --- /dev/null +++ b/pallets/staking/src/pallet/mod.rs @@ -0,0 +1,1781 @@ +// 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 new file mode 100644 index 000000000..1315b27bc --- /dev/null +++ b/pallets/staking/src/slashing.rs @@ -0,0 +1,861 @@ +// 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 new file mode 100644 index 000000000..6182f66f0 --- /dev/null +++ b/pallets/staking/src/testing_utils.rs @@ -0,0 +1,240 @@ +// 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 new file mode 100644 index 000000000..6c1514376 --- /dev/null +++ b/pallets/staking/src/tests.rs @@ -0,0 +1,5847 @@ +// 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 new file mode 100644 index 000000000..f2c65e677 --- /dev/null +++ b/pallets/staking/src/weights.rs @@ -0,0 +1,1506 @@ +// 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 675b7358e..cc3c7c50f 100644 --- a/runtime/cere-dev/Cargo.toml +++ b/runtime/cere-dev/Cargo.toml @@ -52,16 +52,13 @@ 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-society = { workspace = true } pallet-staking = { workspace = true } pallet-staking-reward-curve = { workspace = true } @@ -206,12 +203,9 @@ 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-society/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-ddc-customers/runtime-benchmarks", diff --git a/runtime/cere-dev/src/lib.rs b/runtime/cere-dev/src/lib.rs index 06fc6781d..98280a0aa 100644 --- a/runtime/cere-dev/src/lib.rs +++ b/runtime/cere-dev/src/lib.rs @@ -133,7 +133,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 51300, + spec_version: 51301, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 15, @@ -1032,12 +1032,7 @@ impl pallet_contracts::Config for Runtime { type MaxStorageKeyLen = ConstU32<128>; type UnsafeUnstableInterface = ConstBool; type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; - type Migrations = ( - pallet_contracts::migration::v9::Migration, - pallet_contracts::migration::v10::Migration, - pallet_contracts::migration::v11::Migration, - pallet_contracts::migration::v12::Migration, - ); + type Migrations = (); } impl pallet_sudo::Config for Runtime { @@ -1475,7 +1470,14 @@ pub type SignedExtra = ( pub struct StakingMigrationV11OldPallet; impl Get<&'static str> for StakingMigrationV11OldPallet { fn get() -> &'static str { - "VoterList" + "BagsList" + } +} + +pub struct MigrateStakingPalletToV8; +impl OnRuntimeUpgrade for MigrateStakingPalletToV8 { + fn on_runtime_upgrade() -> Weight { + pallet_staking::migrations::v8::migrate::() } } @@ -1504,7 +1506,15 @@ impl OnRuntimeUpgrade for SetBalancesStorageVersions { } /// Runtime migrations -type Migrations = SetBalancesStorageVersions; +type Migrations = ( + MigrateStakingPalletToV8, + 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, + SetBalancesStorageVersions, +); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< @@ -1560,12 +1570,9 @@ 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] @@ -1905,10 +1912,7 @@ 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; @@ -1928,19 +1932,13 @@ 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 7279d1aff..a1e7084b0 100644 --- a/runtime/cere/Cargo.toml +++ b/runtime/cere/Cargo.toml @@ -52,16 +52,13 @@ 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-society = { workspace = true } pallet-staking = { workspace = true } pallet-staking-reward-curve = { workspace = true } @@ -209,13 +206,10 @@ 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-society/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", diff --git a/runtime/cere/src/lib.rs b/runtime/cere/src/lib.rs index 89dc3dc3f..9234f041f 100644 --- a/runtime/cere/src/lib.rs +++ b/runtime/cere/src/lib.rs @@ -128,7 +128,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 51300, + spec_version: 51301, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 15, @@ -1037,12 +1037,7 @@ impl pallet_contracts::Config for Runtime { type MaxStorageKeyLen = ConstU32<128>; type UnsafeUnstableInterface = ConstBool; type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; - type Migrations = ( - pallet_contracts::migration::v9::Migration, - pallet_contracts::migration::v10::Migration, - pallet_contracts::migration::v11::Migration, - pallet_contracts::migration::v12::Migration, - ); + type Migrations = (); } impl pallet_sudo::Config for Runtime { @@ -1480,7 +1475,14 @@ pub type SignedExtra = ( pub struct StakingMigrationV11OldPallet; impl Get<&'static str> for StakingMigrationV11OldPallet { fn get() -> &'static str { - "VoterList" + "BagsList" + } +} + +pub struct MigrateStakingPalletToV8; +impl OnRuntimeUpgrade for MigrateStakingPalletToV8 { + fn on_runtime_upgrade() -> Weight { + pallet_staking::migrations::v8::migrate::() } } @@ -1508,7 +1510,15 @@ pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Runtime migrations -type Migrations = SetBalancesStorageVersions; +type Migrations = ( + MigrateStakingPalletToV8, + 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, + SetBalancesStorageVersions, +); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< @@ -1556,12 +1566,9 @@ 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] @@ -1895,10 +1902,7 @@ 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; @@ -1918,19 +1922,13 @@ 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/rust-toolchain.toml b/rust-toolchain.toml index baf01c430..b358f6e8e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2023-05-23" +channel = "nightly-2024-03-12" components = ["clippy", "rustfmt"] targets = ["wasm32-unknown-unknown"] diff --git a/scripts/init.sh b/scripts/init.sh index 93f28ffdb..d7eaa58b2 100755 --- a/scripts/init.sh +++ b/scripts/init.sh @@ -4,9 +4,9 @@ set -e echo "*** Initializing WASM build environment" -rustup install nightly-2023-05-23 +rustup install nightly-2024-03-12 -rustup target add wasm32-unknown-unknown --toolchain nightly-2023-05-23 +rustup target add wasm32-unknown-unknown --toolchain nightly-2024-03-12 ln -sf $PWD/scripts/pre-commit.sh $PWD/.git/hooks/pre-commit || true ln -sf $PWD/scripts/pre-push.sh $PWD/.git/hooks/pre-push || true