From bdd139989cb4dfa8cc6cc6b5ee470a711df32d25 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sun, 31 Jul 2022 13:27:13 +0100 Subject: [PATCH 01/81] add failing test for itamar --- frame/staking/src/tests.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index d14d8c4a75f2e..646645fdf56a0 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5102,6 +5102,21 @@ fn proportional_ledger_slash_works() { 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, 99), c(6, 100 - 7), c(7, 100 - 7)]); + // ISSUE: The sum of everything we round down is affecting chunk 5, which should have ideally + // remained unchanged. + assert_eq!(ledger.total, 4 * 100 - 15); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 99), (6, 93), (7, 93)])); + panic!(); + // Given ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; ledger.active = 500; From 14486b6cdebc98f79b65f74ecf39bdff401edf85 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sun, 31 Jul 2022 17:48:16 +0100 Subject: [PATCH 02/81] an ugly example of fast unstake --- Cargo.lock | 20 +++ Cargo.toml | 1 + frame/fast-unstake/Cargo.toml | 50 ++++++ frame/fast-unstake/README.md | 240 +++++++++++++++++++++++++ frame/fast-unstake/src/benchmarking.rs | 78 ++++++++ frame/fast-unstake/src/lib.rs | 128 +++++++++++++ frame/fast-unstake/src/tests.rs | 200 +++++++++++++++++++++ frame/fast-unstake/src/weights.rs | 101 +++++++++++ 8 files changed, 818 insertions(+) create mode 100644 frame/fast-unstake/Cargo.toml create mode 100644 frame/fast-unstake/README.md create mode 100644 frame/fast-unstake/src/benchmarking.rs create mode 100644 frame/fast-unstake/src/lib.rs create mode 100644 frame/fast-unstake/src/tests.rs create mode 100644 frame/fast-unstake/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 2b76f61df7912..6e93840cd13a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5658,6 +5658,26 @@ dependencies = [ "sp-tasks", ] +[[package]] +name = "pallet-fast-unstake" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "pallet-nomination-pools", + "pallet-staking", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", +] + [[package]] name = "pallet-gilt" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 1f22343c002a8..9bdce012e8285 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ members = [ "frame/contracts/rpc/runtime-api", "frame/conviction-voting", "frame/democracy", + "frame/fast-unstake", "frame/try-runtime", "frame/election-provider-multi-phase", "frame/election-provider-support", diff --git a/frame/fast-unstake/Cargo.toml b/frame/fast-unstake/Cargo.toml new file mode 100644 index 0000000000000..1b435559de6e1 --- /dev/null +++ b/frame/fast-unstake/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "pallet-fast-unstake" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Unlicense" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME example pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-staking = { default-features = false, path = "../../primitives/staking" } +pallet-staking = { default-features = false, path = "../staking" } +pallet-nomination-pools = { default-features = false, path = "../nomination-pools" } + +[dev-dependencies] +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances/std", + "scale-info/std", + "sp-io/std", + "sp-staking/std", + "sp-runtime/std", + "sp-std/std", + "pallet-staking/std", +] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/fast-unstake/README.md b/frame/fast-unstake/README.md new file mode 100644 index 0000000000000..358829192f11d --- /dev/null +++ b/frame/fast-unstake/README.md @@ -0,0 +1,240 @@ + +# Basic Example Pallet + + +The Example: A simple example of a FRAME pallet demonstrating +concepts, APIs and structures common to most FRAME runtimes. + +Run `cargo doc --package pallet-example-basic --open` to view this pallet's documentation. + +**This pallet serves as an example and is not meant to be used in production.** + +### Documentation Guidelines: + + + +
    +
  • Documentation comments (i.e. /// comment) - should + accompany pallet functions and be restricted to the pallet interface, + not the internals of the pallet implementation. Only state inputs, + outputs, and a brief description that mentions whether calling it + requires root, but without repeating the source code details. + Capitalize the first word of each documentation comment and end it with + a full stop. See + Generic example of annotating source code with documentation comments
  • +
  • Self-documenting code - Try to refactor code to be self-documenting.
  • +
  • Code comments - Supplement complex code with a brief explanation, not every line of code.
  • +
  • Identifiers - surround by backticks (i.e. INHERENT_IDENTIFIER, InherentType, + u64)
  • +
  • Usage scenarios - should be simple doctests. The compiler should ensure they stay valid.
  • +
  • Extended tutorials - should be moved to external files and refer to.
  • + +
  • Mandatory - include all of the sections/subsections where MUST is specified.
  • +
  • Optional - optionally include sections/subsections where CAN is specified.
  • +
+ +### Documentation Template:
+ +Copy and paste this template from frame/examples/basic/src/lib.rs into file +`frame//src/lib.rs` of your own custom pallet and complete it. +

+// Add heading with custom pallet name
+
+\#  Pallet
+
+// Add simple description
+
+// Include the following links that shows what trait needs to be implemented to use the pallet
+// and the supported dispatchables that are documented in the Call enum.
+
+- \[`::Config`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/trait.Config.html)
+- \[`Call`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/enum.Call.html)
+- \[`Module`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/struct.Module.html)
+
+\## Overview
+
+
+// Short description of pallet's purpose.
+// Links to Traits that should be implemented.
+// What this pallet is for.
+// What functionality the pallet provides.
+// When to use the pallet (use case examples).
+// How it is used.
+// Inputs it uses and the source of each input.
+// Outputs it produces.
+
+
+
+
+\## Terminology
+
+// Add terminology used in the custom pallet. Include concepts, storage items, or actions that you think
+// deserve to be noted to give context to the rest of the documentation or pallet usage. The author needs to
+// use some judgment about what is included. We don't want a list of every storage item nor types - the user
+// can go to the code for that. For example, "transfer fee" is obvious and should not be included, but
+// "free balance" and "reserved balance" should be noted to give context to the pallet.
+// Please do not link to outside resources. The reference docs should be the ultimate source of truth.
+
+
+
+\## Goals
+
+// Add goals that the custom pallet is designed to achieve.
+
+
+
+\### Scenarios
+
+
+
+\#### 
+
+// Describe requirements prior to interacting with the custom pallet.
+// Describe the process of interacting with the custom pallet for this scenario and public API functions used.
+
+\## Interface
+
+\### Supported Origins
+
+// What origins are used and supported in this pallet (root, signed, none)
+// i.e. root when \`ensure_root\` used
+// i.e. none when \`ensure_none\` used
+// i.e. signed when \`ensure_signed\` used
+
+\`inherent\` 
+
+
+
+
+\### Types
+
+// Type aliases. Include any associated types and where the user would typically define them.
+
+\`ExampleType\` 
+
+
+
+// Reference documentation of aspects such as `storageItems` and `dispatchable` functions should only be
+// included in the https://docs.rs Rustdocs for Substrate and not repeated in the README file.
+
+\### Dispatchable Functions
+
+
+
+// A brief description of dispatchable functions and a link to the rustdoc with their actual documentation.
+
+// MUST have link to Call enum
+// MUST have origin information included in function doc
+// CAN have more info up to the user
+
+\### Public Functions
+
+
+
+// A link to the rustdoc and any notes about usage in the pallet, not for specific functions.
+// For example, in the Balances Pallet: "Note that when using the publicly exposed functions,
+// you (the runtime developer) are responsible for implementing any necessary checks
+// (e.g. that the sender is the signer) before calling a function that will affect storage."
+
+
+
+// It is up to the writer of the respective pallet (with respect to how much information to provide).
+
+\#### Public Inspection functions - Immutable (getters)
+
+// Insert a subheading for each getter function signature
+
+\##### \`example_getter_name()\`
+
+// What it returns
+// Why, when, and how often to call it
+// When it could panic or error
+// When safety issues to consider
+
+\#### Public Mutable functions (changing state)
+
+// Insert a subheading for each setter function signature
+
+\##### \`example_setter_name(origin, parameter_name: T::ExampleType)\`
+
+// What state it changes
+// Why, when, and how often to call it
+// When it could panic or error
+// When safety issues to consider
+// What parameter values are valid and why
+
+\### Storage Items
+
+// Explain any storage items included in this pallet
+
+\### Digest Items
+
+// Explain any digest items included in this pallet
+
+\### Inherent Data
+
+// Explain what inherent data (if any) is defined in the pallet and any other related types
+
+\### Events:
+
+// Insert events for this pallet if any
+
+\### Errors:
+
+// Explain what generates errors
+
+\## Usage
+
+// Insert 2-3 examples of usage and code snippets that show how to
+// use  Pallet in a custom pallet.
+
+\### Prerequisites
+
+// Show how to include necessary imports for  and derive
+// your pallet configuration trait with the `INSERT_CUSTOM_PALLET_NAME` trait.
+
+\```rust
+use ;
+
+pub trait Config: ::Config { }
+\```
+
+\### Simple Code Snippet
+
+// Show a simple example (e.g. how to query a public getter function of )
+
+\### Example from FRAME
+
+// Show a usage example in an actual runtime
+
+// See:
+// - Substrate TCR https://github.com/parity-samples/substrate-tcr
+// - Substrate Kitties https://shawntabrizi.github.io/substrate-collectables-workshop/#/
+
+\## Genesis Config
+
+
+
+\## Dependencies
+
+// Dependencies on other FRAME pallets and the genesis config should be mentioned,
+// but not the Rust Standard Library.
+// Genesis configuration modifications that may be made to incorporate this pallet
+// Interaction with other pallets
+
+
+
+\## Related Pallets
+
+// Interaction with other pallets in the form of a bullet point list
+
+\## References
+
+
+
+// Links to reference material, if applicable. For example, Phragmen, W3F research, etc.
+// that the implementation is based on.
+

+ +License: Unlicense diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs new file mode 100644 index 0000000000000..d7b933577ead5 --- /dev/null +++ b/frame/fast-unstake/src/benchmarking.rs @@ -0,0 +1,78 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Benchmarking for pallet-example-basic. + +#![cfg(feature = "runtime-benchmarks")] + +use crate::*; +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_system::RawOrigin; + +// To actually run this benchmark on pallet-example-basic, we need to put this pallet into the +// runtime and compile it with `runtime-benchmarks` feature. The detail procedures are +// documented at: +// https://docs.substrate.io/v3/runtime/benchmarking#how-to-benchmark +// +// The auto-generated weight estimate of this pallet is copied over to the `weights.rs` file. +// The exact command of how the estimate generated is printed at the top of the file. + +// Details on using the benchmarks macro can be seen at: +// https://paritytech.github.io/substrate/master/frame_benchmarking/trait.Benchmarking.html#tymethod.benchmarks +benchmarks! { + // This will measure the execution time of `set_dummy` for b in [1..1000] range. + set_dummy_benchmark { + // This is the benchmark setup phase + let b in 1 .. 1000; + }: set_dummy(RawOrigin::Root, b.into()) // The execution phase is just running `set_dummy` extrinsic call + verify { + // This is the optional benchmark verification phase, asserting certain states. + assert_eq!(Pallet::::dummy(), Some(b.into())) + } + + // This will measure the execution time of `accumulate_dummy` for b in [1..1000] range. + // The benchmark execution phase is shorthanded. When the name of the benchmark case is the same + // as the extrinsic call. `_(...)` is used to represent the extrinsic name. + // The benchmark verification phase is omitted. + accumulate_dummy { + let b in 1 .. 1000; + // The caller account is whitelisted for DB reads/write by the benchmarking macro. + let caller: T::AccountId = whitelisted_caller(); + }: _(RawOrigin::Signed(caller), b.into()) + + // This will measure the execution time of sorting a vector. + sort_vector { + let x in 0 .. 10000; + let mut m = Vec::::new(); + for i in (0..x).rev() { + m.push(i); + } + }: { + // The benchmark execution phase could also be a closure with custom code + m.sort_unstable(); + } + + // This line generates test cases for benchmarking, and could be run by: + // `cargo test -p pallet-example-basic --all-features`, you will see one line per case: + // `test benchmarking::bench_sort_vector ... ok` + // `test benchmarking::bench_accumulate_dummy ... ok` + // `test benchmarking::bench_set_dummy_benchmark ... ok` in the result. + // + // The line generates three steps per benchmark, with repeat=1 and the three steps are + // [low, mid, high] of the range. + impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test) +} diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs new file mode 100644 index 0000000000000..cd30e09969818 --- /dev/null +++ b/frame/fast-unstake/src/lib.rs @@ -0,0 +1,128 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_std::prelude::*; + + use frame_support::traits::Currency; + use pallet_nomination_pools::PoolId; + use sp_staking::EraIndex; + + type BalanceOf = <::Currency as Currency< + ::AccountId, + >>::Balance; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: + frame_system::Config + pallet_staking::Config + pallet_nomination_pools::Config + { + type SlashPerEra: Get>; + } + + #[derive(Encode, Decode, Eq, PartialEq, Clone)] + pub struct Unstake { + stash: AccountId, + checked: Vec, + pool_id: PoolId, + } + + #[pallet::storage] + #[pallet::unbounded] + pub type Head = StorageValue<_, Unstake, OptionQuery>; + + #[pallet::storage] + pub type Queue = StorageMap<_, Twox64Concat, T::AccountId, PoolId>; + + #[pallet::storage] + pub type ErasToCheckPerBlock = StorageValue<_, u32, ValueQuery>; + + #[pallet::hooks] + impl Hooks for Pallet { + fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight { + 0 + } + } + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + pub fn enqueue(origin: OriginFor, pool_id: PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; + } + } + + impl Pallet { + fn process_head() { + let maybe_next = Head::::take().or_else(|| { + Queue::::drain().take(1).map(|(stash, pool_id)| Unstake { stash, pool_id, checked: Default::default() }).next() + }); + + let Unstake { stash, checked, pool_id } = match maybe_next { + None => return, + Some(x) => x, + }; + + let current_era = pallet_staking::CurrentEra::::get().unwrap_or_default(); + let bonding_duration = ::BondingDuration::get(); + + let total_check_range = (current_era.saturating_sub(bonding_duration)..current_era) + .rev() + .collect::>(); + let now_check_range = total_check_range + .iter() + .filter(|e| !checked.contains(e)) + .take(ErasToCheckPerBlock::::get() as usize) + .collect::>(); + + if now_check_range.is_empty() { + // `stash` is not exposed in any era -- we can let go of them now. + let num_slashing_spans = 0; // TODO + let ctrl = pallet_staking::Bonded::::get(stash).unwrap(); + let ledger = pallet_staking::Ledger::::get(ctrl).unwrap(); + pallet_staking::Pallet::::force_unstake(Origin::Root, stash, num_slashing_spans) + .unwrap(); + pallet_nomination_pools::Pallet::::join(Origin::Signed(stash), ledger.total, pool_id); + } + + let is_exposed = now_check_range.iter().any(|e| Self::is_exposed_in_era(&stash, *e)); + + if is_exposed { + // this account was actually exposed in some era within the range -- slash them and + // remove them from the queue. + // TODO: slash + } else { + // Not exposed in these two eras. + checked.extend(now_check_range); + Head::::put(Unstake { stash, checked, pool_id }); + } + } + + fn is_exposed_in_era(who: &T::AccountId, era: &EraIndex) -> bool { + pallet_staking::ErasStakers::::iter_prefix(era) + .any(|(_, exposures)| exposures.others.iter().any(|i| i.who == *who)) + } + } +} diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs new file mode 100644 index 0000000000000..0f659e12fb443 --- /dev/null +++ b/frame/fast-unstake/src/tests.rs @@ -0,0 +1,200 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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 pallet-example-basic. + +use crate::*; +use frame_support::{ + assert_ok, parameter_types, + traits::{ConstU64, OnInitialize}, + weights::{DispatchInfo, GetDispatchInfo}, +}; +use sp_core::H256; +// The testing primitives are very useful for avoiding having to work with signatures +// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; +// Reexport crate as its pallet name for construct_runtime. +use crate as pallet_example_basic; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +// For testing the pallet, we construct a mock runtime. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Example: pallet_example_basic::{Pallet, Call, Storage, Config, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(1024); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Call = Call; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = 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 = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = u64; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); +} + +impl Config for Test { + type MagicNumber = ConstU64<1_000_000_000>; + type Event = Event; + type WeightInfo = (); +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = GenesisConfig { + // We use default for brevity, but you can configure as desired if needed. + system: Default::default(), + balances: Default::default(), + example: pallet_example_basic::GenesisConfig { + dummy: 42, + // we configure the map with (key, value) pairs. + bar: vec![(1, 2), (2, 3)], + foo: 24, + }, + } + .build_storage() + .unwrap(); + t.into() +} + +#[test] +fn it_works_for_optional_value() { + new_test_ext().execute_with(|| { + // Check that GenesisBuilder works properly. + let val1 = 42; + let val2 = 27; + assert_eq!(Example::dummy(), Some(val1)); + + // Check that accumulate works when we have Some value in Dummy already. + assert_ok!(Example::accumulate_dummy(Origin::signed(1), val2)); + assert_eq!(Example::dummy(), Some(val1 + val2)); + + // Check that accumulate works when we Dummy has None in it. + >::on_initialize(2); + assert_ok!(Example::accumulate_dummy(Origin::signed(1), val1)); + assert_eq!(Example::dummy(), Some(val1 + val2 + val1)); + }); +} + +#[test] +fn it_works_for_default_value() { + new_test_ext().execute_with(|| { + assert_eq!(Example::foo(), 24); + assert_ok!(Example::accumulate_foo(Origin::signed(1), 1)); + assert_eq!(Example::foo(), 25); + }); +} + +#[test] +fn set_dummy_works() { + new_test_ext().execute_with(|| { + let test_val = 133; + assert_ok!(Example::set_dummy(Origin::root(), test_val.into())); + assert_eq!(Example::dummy(), Some(test_val)); + }); +} + +#[test] +fn signed_ext_watch_dummy_works() { + new_test_ext().execute_with(|| { + let call = pallet_example_basic::Call::set_dummy { new_value: 10 }.into(); + let info = DispatchInfo::default(); + + assert_eq!( + WatchDummy::(PhantomData) + .validate(&1, &call, &info, 150) + .unwrap() + .priority, + u64::MAX, + ); + assert_eq!( + WatchDummy::(PhantomData).validate(&1, &call, &info, 250), + InvalidTransaction::ExhaustsResources.into(), + ); + }) +} + +#[test] +fn counted_map_works() { + new_test_ext().execute_with(|| { + assert_eq!(CountedMap::::count(), 0); + CountedMap::::insert(3, 3); + assert_eq!(CountedMap::::count(), 1); + }) +} + +#[test] +fn weights_work() { + // must have a defined weight. + let default_call = pallet_example_basic::Call::::accumulate_dummy { increase_by: 10 }; + let info1 = default_call.get_dispatch_info(); + // aka. `let info = as GetDispatchInfo>::get_dispatch_info(&default_call);` + assert!(info1.weight > 0); + + // `set_dummy` is simpler than `accumulate_dummy`, and the weight + // should be less. + let custom_call = pallet_example_basic::Call::::set_dummy { new_value: 20 }; + let info2 = custom_call.get_dispatch_info(); + assert!(info1.weight > info2.weight); +} diff --git a/frame/fast-unstake/src/weights.rs b/frame/fast-unstake/src/weights.rs new file mode 100644 index 0000000000000..5fc6434e396eb --- /dev/null +++ b/frame/fast-unstake/src/weights.rs @@ -0,0 +1,101 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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_example_basic +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-03-15, STEPS: `[100, ]`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// ./target/release/substrate +// benchmark +// --chain +// dev +// --execution +// wasm +// --wasm-execution +// compiled +// --pallet +// pallet_example_basic +// --extrinsic +// * +// --steps +// 100 +// --repeat +// 10 +// --raw +// --output +// ./ +// --template +// ./.maintain/frame-weight-template.hbs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_example_basic. +pub trait WeightInfo { + fn set_dummy_benchmark(b: u32, ) -> Weight; + fn accumulate_dummy(b: u32, ) -> Weight; + fn sort_vector(x: u32, ) -> Weight; +} + +/// Weights for pallet_example_basic using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn set_dummy_benchmark(b: u32, ) -> Weight { + (5_834_000 as Weight) + .saturating_add((24_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn accumulate_dummy(b: u32, ) -> Weight { + (51_353_000 as Weight) + .saturating_add((14_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn sort_vector(x: u32, ) -> Weight { + (2_569_000 as Weight) + // Standard Error: 0 + .saturating_add((4_000 as Weight).saturating_mul(x as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn set_dummy_benchmark(b: u32, ) -> Weight { + (5_834_000 as Weight) + .saturating_add((24_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn accumulate_dummy(b: u32, ) -> Weight { + (51_353_000 as Weight) + .saturating_add((14_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn sort_vector(x: u32, ) -> Weight { + (2_569_000 as Weight) + // Standard Error: 0 + .saturating_add((4_000 as Weight).saturating_mul(x as Weight)) + } +} From 960c06726bae6fab5d3cfce330aa6db447cc608d Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sun, 31 Jul 2022 17:48:23 +0100 Subject: [PATCH 03/81] Revert "add failing test for itamar" This reverts commit bdd139989cb4dfa8cc6cc6b5ee470a711df32d25. --- frame/staking/src/tests.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 646645fdf56a0..d14d8c4a75f2e 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5102,21 +5102,6 @@ fn proportional_ledger_slash_works() { 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, 99), c(6, 100 - 7), c(7, 100 - 7)]); - // ISSUE: The sum of everything we round down is affecting chunk 5, which should have ideally - // remained unchanged. - assert_eq!(ledger.total, 4 * 100 - 15); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 99), (6, 93), (7, 93)])); - panic!(); - // Given ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; ledger.active = 500; From 85fb600d402a7e49b3baef5876e44a4c69a9c4c7 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 2 Aug 2022 19:49:03 +0100 Subject: [PATCH 04/81] fast unstake wip --- bin/node/runtime/src/lib.rs | 2 +- frame/fast-unstake/src/lib.rs | 66 +++++++++++++++++++++++------------ frame/staking/src/lib.rs | 2 +- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 2ac1e6444f119..0b1bb07aef1b8 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1403,7 +1403,7 @@ impl pallet_assets::Config for Runtime { type Balance = u128; type AssetId = u32; type Currency = Balances; - type ForceOrigin = EnsureRoot; + type ForceOrigin = EnsureSigned; type AssetDeposit = AssetDeposit; type AssetAccountDeposit = ConstU128; type MetadataDepositBase = MetadataDepositBase; diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index cd30e09969818..417449d6fa640 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -15,7 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] #[frame_support::pallet] @@ -27,6 +26,7 @@ pub mod pallet { use frame_support::traits::Currency; use pallet_nomination_pools::PoolId; use sp_staking::EraIndex; + use sp_runtime::traits::Saturating; type BalanceOf = <::Currency as Currency< ::AccountId, @@ -37,12 +37,15 @@ pub mod pallet { #[pallet::config] pub trait Config: - frame_system::Config + pallet_staking::Config + pallet_nomination_pools::Config + frame_system::Config + + pallet_staking::Config< + CurrencyBalance = ::CurrencyBalance, + > + pallet_nomination_pools::Config { type SlashPerEra: Get>; } - #[derive(Encode, Decode, Eq, PartialEq, Clone)] + #[derive(Encode, Decode, Eq, PartialEq, Clone, scale_info::TypeInfo)] pub struct Unstake { stash: AccountId, checked: Vec, @@ -70,21 +73,32 @@ pub mod pallet { impl Pallet { #[pallet::weight(0)] pub fn enqueue(origin: OriginFor, pool_id: PoolId) -> DispatchResult { + // TODO: they must not already have any unbonding funds. let who = ensure_signed(origin)?; + todo!(); } } impl Pallet { fn process_head() { + let eras_to_check = ErasToCheckPerBlock::::get(); let maybe_next = Head::::take().or_else(|| { - Queue::::drain().take(1).map(|(stash, pool_id)| Unstake { stash, pool_id, checked: Default::default() }).next() + Queue::::drain() + .take(1) + .map(|(stash, pool_id)| Unstake { stash, pool_id, checked: Default::default() }) + .next() }); - let Unstake { stash, checked, pool_id } = match maybe_next { + let Unstake { stash, mut checked, pool_id } = match maybe_next { None => return, Some(x) => x, }; + let slash_stash = |eras_checked: EraIndex| { + let slash_amount = T::SlashPerEra::get().saturating_mul(eras_checked.into()); + let (_, imbalance) = ::Currency::slash(&stash, slash_amount); + }; + let current_era = pallet_staking::CurrentEra::::get().unwrap_or_default(); let bonding_duration = ::BondingDuration::get(); @@ -94,30 +108,38 @@ pub mod pallet { let now_check_range = total_check_range .iter() .filter(|e| !checked.contains(e)) - .take(ErasToCheckPerBlock::::get() as usize) + .take(eras_to_check as usize) .collect::>(); - if now_check_range.is_empty() { + if now_check_range.is_empty() && eras_to_check > 0 { // `stash` is not exposed in any era -- we can let go of them now. let num_slashing_spans = 0; // TODO - let ctrl = pallet_staking::Bonded::::get(stash).unwrap(); + let ctrl = pallet_staking::Bonded::::get(&stash).unwrap(); let ledger = pallet_staking::Ledger::::get(ctrl).unwrap(); - pallet_staking::Pallet::::force_unstake(Origin::Root, stash, num_slashing_spans) - .unwrap(); - pallet_nomination_pools::Pallet::::join(Origin::Signed(stash), ledger.total, pool_id); - } - - let is_exposed = now_check_range.iter().any(|e| Self::is_exposed_in_era(&stash, *e)); - - if is_exposed { - // this account was actually exposed in some era within the range -- slash them and - // remove them from the queue. - // TODO: slash + pallet_staking::Pallet::::force_unstake( + frame_system::RawOrigin::Root.into(), + stash.clone(), + num_slashing_spans, + ) + .unwrap(); + pallet_nomination_pools::Pallet::::join( + frame_system::RawOrigin::Signed(stash.clone()).into(), + ledger.total, + pool_id, + ).unwrap(); } else { - // Not exposed in these two eras. - checked.extend(now_check_range); - Head::::put(Unstake { stash, checked, pool_id }); + let is_exposed = now_check_range.iter().any(|e| Self::is_exposed_in_era(&stash, *e)); + if is_exposed { + // this account was actually exposed in some era within the range -- slash them and + // remove them from the queue. + // TODO: slash + } else { + // Not exposed in these two eras. + checked.extend(now_check_range); + Head::::put(Unstake { stash, checked, pool_id }); + } } + } fn is_exposed_in_era(who: &T::AccountId, era: &EraIndex) -> bool { diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index ab0ab685e6911..f58bcc3db71c1 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -552,7 +552,7 @@ impl StakingLedger { /// /// This calls `Config::OnStakerSlash::on_slash` with information as to how the slash was /// applied. - fn slash( + pub fn slash( &mut self, slash_amount: BalanceOf, minimum_balance: BalanceOf, From 87a712eb3d9034c7eb5bd493cffef22180920c59 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 19 Aug 2022 15:18:43 +0430 Subject: [PATCH 05/81] clean it up a bit --- frame/fast-unstake/src/lib.rs | 60 +++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 417449d6fa640..ad5a788820fe5 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -15,6 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! A pallet that's designed to ONLY do: +//! +//! If a nominator is not exposed at all in any `ErasStakers` (i.e. "has not backed any validators in the last 28 days"), then they can register themselves in this pallet, and move quickly into a nomination pool. + #![cfg_attr(not(feature = "std"), no_std)] #[frame_support::pallet] @@ -45,20 +49,31 @@ pub mod pallet { type SlashPerEra: Get>; } + /// One who wishes to be unstaked. #[derive(Encode, Decode, Eq, PartialEq, Clone, scale_info::TypeInfo)] pub struct Unstake { + /// Their stash account. stash: AccountId, + /// The list of eras for which they have been checked. checked: Vec, + /// The pool they wish to join. pool_id: PoolId, } + /// The current "head of the queue" being unstaked. #[pallet::storage] #[pallet::unbounded] pub type Head = StorageValue<_, Unstake, OptionQuery>; + /// The map of all accounts wishing to be unstaked. #[pallet::storage] pub type Queue = StorageMap<_, Twox64Concat, T::AccountId, PoolId>; + /// Number of eras to check per block. + /// + /// If set to 0, this pallet does absolutely nothing. + /// + /// Based on the amount of weight available ot `on_idle`, up to this many eras of a single nominator might be checked. #[pallet::storage] pub type ErasToCheckPerBlock = StorageValue<_, u32, ValueQuery>; @@ -71,29 +86,52 @@ pub mod pallet { #[pallet::call] impl Pallet { + /// enqueue oneself to be migrated from #[pallet::weight(0)] pub fn enqueue(origin: OriginFor, pool_id: PoolId) -> DispatchResult { - // TODO: they must not already have any unbonding funds. + // TODO: they must not already have any unbonding funds, i.e. ledger.unlocking + // TODO: they should not be able to perform any actions in staking anymore once they are enqueued.. this might be a bit nasty. Should use a custom signed extension. let who = ensure_signed(origin)?; todo!(); } } impl Pallet { - fn process_head() { - let eras_to_check = ErasToCheckPerBlock::::get(); - let maybe_next = Head::::take().or_else(|| { + fn get_unstake_head() -> Option> { + Head::::take().or_else(|| { Queue::::drain() .take(1) .map(|(stash, pool_id)| Unstake { stash, pool_id, checked: Default::default() }) .next() - }); + }) + } + /// process up to `remaining_weight`. + /// + /// Returns the actual weight consumed. + fn process_head(remaining_weight: Weight) -> Weight { + let get_unstake_head_weight = T::DbWeight::get().reads(2); + if remaining_weight < get_unstake_head_weight { + // nothing can be done. + return 0; + } + - let Unstake { stash, mut checked, pool_id } = match maybe_next { - None => return, + let Unstake { stash, mut checked, pool_id } = match Self::get_unstake_head() { + None => { + // There's no `Head` and nothing in the `Queue`, nothing to do here. + return get_unstake_head_weight; + }, Some(x) => x, }; + let weight_per_era_check = todo!("should come from our benchmarks"); + let max_eras_to_check = remaining_weight.div(weight_per_era_check); + let final_eras_to_check = ErasToCheckPerBlock::::get().min(max_eras_to_check); + + if final_eras_to_check.is_zero() { + return get_unstake_head_weight + T::DbWeight::get().reads(1) + } + let slash_stash = |eras_checked: EraIndex| { let slash_amount = T::SlashPerEra::get().saturating_mul(eras_checked.into()); let (_, imbalance) = ::Currency::slash(&stash, slash_amount); @@ -108,10 +146,10 @@ pub mod pallet { let now_check_range = total_check_range .iter() .filter(|e| !checked.contains(e)) - .take(eras_to_check as usize) + .take(final_eras_to_check as usize) .collect::>(); - if now_check_range.is_empty() && eras_to_check > 0 { + if now_check_range.is_empty() && final_eras_to_check > 0 { // `stash` is not exposed in any era -- we can let go of them now. let num_slashing_spans = 0; // TODO let ctrl = pallet_staking::Bonded::::get(&stash).unwrap(); @@ -127,19 +165,21 @@ pub mod pallet { ledger.total, pool_id, ).unwrap(); + 0 // TODO return weight, should be the weight of the code in this `if` } else { let is_exposed = now_check_range.iter().any(|e| Self::is_exposed_in_era(&stash, *e)); if is_exposed { // this account was actually exposed in some era within the range -- slash them and // remove them from the queue. // TODO: slash + 0 // TODO: return weight, should be 'now_check_range.count() * weight_per_era_check + slash_weight' } else { // Not exposed in these two eras. checked.extend(now_check_range); Head::::put(Unstake { stash, checked, pool_id }); + 0 // TODO: return weight, should be 'now_check_range.count() * weight_per_era_check' } } - } fn is_exposed_in_era(who: &T::AccountId, era: &EraIndex) -> bool { From 0449aab7af8a41e5e0aace69c49e8f9ae3c82b1b Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Mon, 22 Aug 2022 10:51:15 +0100 Subject: [PATCH 06/81] some comments --- frame/fast-unstake/src/lib.rs | 69 ++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index ad5a788820fe5..28339d14de638 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -29,8 +29,8 @@ pub mod pallet { use frame_support::traits::Currency; use pallet_nomination_pools::PoolId; - use sp_staking::EraIndex; use sp_runtime::traits::Saturating; + use sp_staking::EraIndex; type BalanceOf = <::Currency as Currency< ::AccountId, @@ -49,7 +49,7 @@ pub mod pallet { type SlashPerEra: Get>; } - /// One who wishes to be unstaked. + /// One who wishes to be unstaked and join a pool. #[derive(Encode, Decode, Eq, PartialEq, Clone, scale_info::TypeInfo)] pub struct Unstake { /// Their stash account. @@ -61,11 +61,13 @@ pub mod pallet { } /// The current "head of the queue" being unstaked. + /// The leading `Unstake` item due to be processed for unstaking. #[pallet::storage] #[pallet::unbounded] pub type Head = StorageValue<_, Unstake, OptionQuery>; /// The map of all accounts wishing to be unstaked. + /// Points the `AccountId` wishing to unstake to the `PoolId` they wish to join thereafter. #[pallet::storage] pub type Queue = StorageMap<_, Twox64Concat, T::AccountId, PoolId>; @@ -73,30 +75,50 @@ pub mod pallet { /// /// If set to 0, this pallet does absolutely nothing. /// - /// Based on the amount of weight available ot `on_idle`, up to this many eras of a single nominator might be checked. + /// Based on the amount of weight available at `on_idle`, up to this many eras of a single + /// nominator might be checked. #[pallet::storage] pub type ErasToCheckPerBlock = StorageValue<_, u32, ValueQuery>; + /// TODO (TODISCUSS?): Could we introduce another storage item to streamline the checking of + /// exposure in eras? Aim: to speed up `is_exposed_in_era()`. + /// Could introduce `HistoricalNominationsEras` storage item that was mentioned here: + /// https://github.com/paritytech/substrate/issues/8436#issuecomment-1212962043 + /// Note: this would require us to append Eras when user `nominate`s (& rm Eras past + /// HISTORY_DEPTH) #[pallet::storage] + // pub type HistoricalNominatorEras = StorageMap<_, Twox64Concat, T::AccountId, + // Vec>; + #[pallet::hooks] impl Hooks for Pallet { fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight { + /// TODO: iterate remaining weight and process outstanding `Unstake` requests. + /// -> Start and Head + /// -> Process entries of Queue as long as remaining_weight allows + /// -> update Head 0 } } #[pallet::call] impl Pallet { - /// enqueue oneself to be migrated from + /// enqueue oneself to be migrated from. #[pallet::weight(0)] pub fn enqueue(origin: OriginFor, pool_id: PoolId) -> DispatchResult { - // TODO: they must not already have any unbonding funds, i.e. ledger.unlocking - // TODO: they should not be able to perform any actions in staking anymore once they are enqueued.. this might be a bit nasty. Should use a custom signed extension. + todo!("assert not already in Queue."); + todo!("assert must be `bonded` (have actively bonded funds) to unstake."); + todo!("they must not already have any unbonding funds, i.e. ledger.unlocking"); + + // TODO: they should not be able to perform any actions in staking anymore once they are + // enqueued. This might be a bit nasty. Should use a custom signed extension. let who = ensure_signed(origin)?; todo!(); } } impl Pallet { + /// Gets the first item of `Queue` or the `Head` Unstake entries if present. + /// Returns `None` if no entries present. fn get_unstake_head() -> Option> { Head::::take().or_else(|| { Queue::::drain() @@ -112,37 +134,42 @@ pub mod pallet { let get_unstake_head_weight = T::DbWeight::get().reads(2); if remaining_weight < get_unstake_head_weight { // nothing can be done. - return 0; + return 0 } - let Unstake { stash, mut checked, pool_id } = match Self::get_unstake_head() { None => { // There's no `Head` and nothing in the `Queue`, nothing to do here. - return get_unstake_head_weight; + return get_unstake_head_weight }, Some(x) => x, }; + // determine the amount of eras to check. let weight_per_era_check = todo!("should come from our benchmarks"); let max_eras_to_check = remaining_weight.div(weight_per_era_check); let final_eras_to_check = ErasToCheckPerBlock::::get().min(max_eras_to_check); + // return weight consumed if no eras to check (1 read). if final_eras_to_check.is_zero() { return get_unstake_head_weight + T::DbWeight::get().reads(1) } let slash_stash = |eras_checked: EraIndex| { let slash_amount = T::SlashPerEra::get().saturating_mul(eras_checked.into()); - let (_, imbalance) = ::Currency::slash(&stash, slash_amount); + let (_, imbalance) = + ::Currency::slash(&stash, slash_amount); }; let current_era = pallet_staking::CurrentEra::::get().unwrap_or_default(); let bonding_duration = ::BondingDuration::get(); + // get the last available `bonding_duration` eras up to current era in reverse order. let total_check_range = (current_era.saturating_sub(bonding_duration)..current_era) .rev() .collect::>(); + + // remove eras that do not exist in `checked`. let now_check_range = total_check_range .iter() .filter(|e| !checked.contains(e)) @@ -164,24 +191,32 @@ pub mod pallet { frame_system::RawOrigin::Signed(stash.clone()).into(), ledger.total, pool_id, - ).unwrap(); - 0 // TODO return weight, should be the weight of the code in this `if` + ) + .unwrap(); + // TODO return weight, should be the weight of the code in this `if`. + // weight(nomination_pools.join) + weight(staking.force_unstake) + 2(read) + 0 } else { - let is_exposed = now_check_range.iter().any(|e| Self::is_exposed_in_era(&stash, *e)); + // eras remaining to be checked. + let is_exposed = + now_check_range.iter().any(|e| Self::is_exposed_in_era(&stash, *e)); if is_exposed { - // this account was actually exposed in some era within the range -- slash them and - // remove them from the queue. + // this account was actually exposed in some era within the range -- slash them + // and remove them from the queue. // TODO: slash - 0 // TODO: return weight, should be 'now_check_range.count() * weight_per_era_check + slash_weight' + 0 // TODO: return weight, should be 'now_check_range.count() * + // weight_per_era_check + slash_weight' } else { // Not exposed in these two eras. checked.extend(now_check_range); Head::::put(Unstake { stash, checked, pool_id }); - 0 // TODO: return weight, should be 'now_check_range.count() * weight_per_era_check' + 0 // TODO: return weight, should be 'now_check_range.count() * + // weight_per_era_check' } } } + /// Checks whether an account `who` has been exposed in an era. fn is_exposed_in_era(who: &T::AccountId, era: &EraIndex) -> bool { pallet_staking::ErasStakers::::iter_prefix(era) .any(|(_, exposures)| exposures.others.iter().any(|i| i.who == *who)) From be760571a08d54f11ab931a3696d2446f7efca84 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Mon, 22 Aug 2022 11:21:13 +0100 Subject: [PATCH 07/81] on_idle logic --- frame/fast-unstake/src/lib.rs | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 28339d14de638..d056611e3d9c3 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -29,9 +29,11 @@ pub mod pallet { use frame_support::traits::Currency; use pallet_nomination_pools::PoolId; - use sp_runtime::traits::Saturating; + use sp_runtime::traits::{Saturating, Zero}; use sp_staking::EraIndex; + use sp_std::{ops::Div, vec::Vec}; + type BalanceOf = <::Currency as Currency< ::AccountId, >>::Balance; @@ -92,11 +94,18 @@ pub mod pallet { #[pallet::hooks] impl Hooks for Pallet { fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight { - /// TODO: iterate remaining weight and process outstanding `Unstake` requests. - /// -> Start and Head - /// -> Process entries of Queue as long as remaining_weight allows - /// -> update Head - 0 + // We'll call `process_head` until 0 weight is returned + let mut remaining = remaining_weight; + loop { + // process head and + let last_consumed_weight = Self::process_head(remaining_weight); + // if nothing was done, break loop + if last_consumed_weight == Weight::from(0 as u64) { + break + } + remaining = remaining.saturating_sub(last_consumed_weight); + } + remaining } } @@ -146,9 +155,9 @@ pub mod pallet { }; // determine the amount of eras to check. - let weight_per_era_check = todo!("should come from our benchmarks"); + let weight_per_era_check: Weight = todo!("should come from our benchmarks"); let max_eras_to_check = remaining_weight.div(weight_per_era_check); - let final_eras_to_check = ErasToCheckPerBlock::::get().min(max_eras_to_check); + let final_eras_to_check = ErasToCheckPerBlock::::get().min(max_eras_to_check as u32); // return weight consumed if no eras to check (1 read). if final_eras_to_check.is_zero() { @@ -157,8 +166,7 @@ pub mod pallet { let slash_stash = |eras_checked: EraIndex| { let slash_amount = T::SlashPerEra::get().saturating_mul(eras_checked.into()); - let (_, imbalance) = - ::Currency::slash(&stash, slash_amount); + let (_, imbalance) = ::Currency::slash(&stash, slash_amount); }; let current_era = pallet_staking::CurrentEra::::get().unwrap_or_default(); @@ -198,8 +206,7 @@ pub mod pallet { 0 } else { // eras remaining to be checked. - let is_exposed = - now_check_range.iter().any(|e| Self::is_exposed_in_era(&stash, *e)); + let is_exposed = now_check_range.iter().any(|e| Self::is_exposed_in_era(&stash, *e)); if is_exposed { // this account was actually exposed in some era within the range -- slash them // and remove them from the queue. From 6afd4ac3e80ebbcb4fffbd419f75cb3da17744ff Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Mon, 22 Aug 2022 11:22:32 +0100 Subject: [PATCH 08/81] fix --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index d056611e3d9c3..942f6bb7480ce 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -98,7 +98,7 @@ pub mod pallet { let mut remaining = remaining_weight; loop { // process head and - let last_consumed_weight = Self::process_head(remaining_weight); + let last_consumed_weight = Self::process_head(remaining); // if nothing was done, break loop if last_consumed_weight == Weight::from(0 as u64) { break From 91f047a3b61f9eda45a90f5bc20caaf0c17bc813 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Mon, 22 Aug 2022 11:27:24 +0100 Subject: [PATCH 09/81] comment --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 942f6bb7480ce..99bc3b0dee1c0 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -177,7 +177,7 @@ pub mod pallet { .rev() .collect::>(); - // remove eras that do not exist in `checked`. + // remove eras that exist in `checked`. let now_check_range = total_check_range .iter() .filter(|e| !checked.contains(e)) From 548b5ccbe57fbb0f2de371e0688903740d5ae187 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 22 Aug 2022 18:28:05 +0430 Subject: [PATCH 10/81] new working version, checks all pass, looking good --- Cargo.lock | 2 +- .../election-provider-multi-phase/src/lib.rs | 11 + frame/election-provider-support/src/lib.rs | 9 +- .../election-provider-support/src/onchain.rs | 8 + frame/fast-unstake/Cargo.toml | 21 +- frame/fast-unstake/src/lib.rs | 429 +++++++++++++----- frame/fast-unstake/src/tests.rs | 75 +-- frame/fast-unstake/src/weights.rs | 101 ----- frame/staking/src/pallet/mod.rs | 1 + 9 files changed, 379 insertions(+), 278 deletions(-) delete mode 100644 frame/fast-unstake/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 8f60f38f40c7a..886c584f4a113 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5686,10 +5686,10 @@ name = "pallet-fast-unstake" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", + "frame-election-provider-support", "frame-support", "frame-system", "log", - "pallet-balances", "pallet-nomination-pools", "pallet-staking", "parity-scale-codec", diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index e1d3cb8ed5dee..e12e6f398d79b 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -318,6 +318,10 @@ impl ElectionProvider for NoFallback { type DataProvider = T::DataProvider; type Error = &'static str; + fn ongoing() -> bool { + false + } + fn elect() -> Result, Self::Error> { // Do nothing, this will enable the emergency phase. Err("NoFallback.") @@ -1572,6 +1576,13 @@ impl ElectionProvider for Pallet { type Error = ElectionError; type DataProvider = T::DataProvider; + fn ongoing() -> bool { + match Self::current_phase() { + Phase::Off => false, + _ => true, + } + } + fn elect() -> Result, Self::Error> { match Self::do_elect() { Ok(supports) => { diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index eee865d0b737b..f43f8db1700b9 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -136,7 +136,7 @@ //! type BlockNumber = BlockNumber; //! type Error = &'static str; //! type DataProvider = T::DataProvider; -//! +//! fn ongoing() -> bool { false } //! fn elect() -> Result, Self::Error> { //! Self::DataProvider::electable_targets(None) //! .map_err(|_| "failed to elect") @@ -370,6 +370,9 @@ pub trait ElectionProvider { BlockNumber = Self::BlockNumber, >; + /// Indicate if this election provider is currently ongoing an asynchronous election or not. + fn ongoing() -> bool; + /// Elect a new set of winners, without specifying any bounds on the amount of data fetched from /// [`Self::DataProvider`]. An implementation could nonetheless impose its own custom limits. /// @@ -420,6 +423,10 @@ where fn elect() -> Result, Self::Error> { Err(" cannot do anything.") } + + fn ongoing() -> bool { + false + } } /// A utility trait for something to implement `ElectionDataProvider` in a sensible way. diff --git a/frame/election-provider-support/src/onchain.rs b/frame/election-provider-support/src/onchain.rs index 62e76c3888822..c1d8ecb3b230b 100644 --- a/frame/election-provider-support/src/onchain.rs +++ b/frame/election-provider-support/src/onchain.rs @@ -138,6 +138,10 @@ impl ElectionProvider for UnboundedExecution { type Error = Error; type DataProvider = T::DataProvider; + fn ongoing() -> bool { + false + } + fn elect() -> Result, Self::Error> { // This should not be called if not in `std` mode (and therefore neither in genesis nor in // testing) @@ -167,6 +171,10 @@ impl ElectionProvider for BoundedExecution { type Error = Error; type DataProvider = T::DataProvider; + fn ongoing() -> bool { + false + } + fn elect() -> Result, Self::Error> { elect_with::(Some(T::VotersBound::get() as usize), Some(T::TargetsBound::get() as usize)) } diff --git a/frame/fast-unstake/Cargo.toml b/frame/fast-unstake/Cargo.toml index 1b435559de6e1..eb5971b5ee836 100644 --- a/frame/fast-unstake/Cargo.toml +++ b/frame/fast-unstake/Cargo.toml @@ -16,16 +16,20 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } log = { version = "0.4.17", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } + frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } + sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-staking = { default-features = false, path = "../../primitives/staking" } + pallet-staking = { default-features = false, path = "../staking" } pallet-nomination-pools = { default-features = false, path = "../nomination-pools" } +frame-election-provider-support = { default-features = false, path = "../election-provider-support" } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } [dev-dependencies] sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } @@ -34,17 +38,22 @@ sp-core = { version = "6.0.0", default-features = false, path = "../../primitive default = ["std"] std = [ "codec/std", - "frame-benchmarking/std", - "frame-support/std", - "frame-system/std", "log/std", - "pallet-balances/std", "scale-info/std", + + "frame-support/std", + "frame-system/std", + "sp-io/std", "sp-staking/std", "sp-runtime/std", "sp-std/std", + "pallet-staking/std", + "pallet-nomination-pools/std", + "frame-election-provider-support/std", + + "frame-benchmarking/std", ] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 942f6bb7480ce..8d87d0428e5bd 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -17,23 +17,57 @@ //! A pallet that's designed to ONLY do: //! -//! If a nominator is not exposed at all in any `ErasStakers` (i.e. "has not backed any validators in the last 28 days"), then they can register themselves in this pallet, and move quickly into a nomination pool. +//! If a nominator is not exposed at all in any `ErasStakers` (i.e. "has not backed any validators +//! in the last 28 days"), then they can register themselves in this pallet, and move quickly into +//! a nomination pool. +//! +//! This pallet works of the basis of `on_idle`, meaning that it provides no guarantee about when it +//! will succeed, if at all. +//! +//! Stakers who are certain about NOT being exposed can register themselves with +//! [`Call::register_fast_unstake`]. This will chill, and fully unbond the staker, and place them in +//! the queue to be checked. +//! +//! Once queued, but not being actively processed, stakers can withdraw their request via +//! [`Call::deregister`]. +//! +//! Once processed, if successful, no additional fees for the checking process is taken, and the +//! staker is instantly unbonded. Optionally, if the have asked to join a pool, their *entire* stake +//! is joined into their pool of choice. +//! +//! If unsuccessful, meaning that the staker was exposed sometime in the last 28 eras they will end +//! up being slashed for the amount of wasted work they have inflicted on the chian. #![cfg_attr(not(feature = "std"), no_std)] +pub use pallet::*; + #[frame_support::pallet] pub mod pallet { use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; + use frame_system::{pallet_prelude::*, RawOrigin}; use sp_std::prelude::*; - use frame_support::traits::Currency; + use frame_support::traits::{Currency, IsSubType}; use pallet_nomination_pools::PoolId; - use sp_runtime::traits::{Saturating, Zero}; + use sp_runtime::{ + traits::{Saturating, Zero}, + transaction_validity::{InvalidTransaction, TransactionValidityError}, + DispatchResult, + }; use sp_staking::EraIndex; + use frame_election_provider_support::ElectionProvider; + use pallet_nomination_pools::WeightInfo as _; + use pallet_staking::{Pallet as Staking, WeightInfo as _}; + use sp_std::{ops::Div, vec::Vec}; + pub trait WeightInfo { + fn weight_per_era_check() -> Weight; + fn do_slash() -> Weight; + } + type BalanceOf = <::Currency as Currency< ::AccountId, >>::Balance; @@ -48,30 +82,43 @@ pub mod pallet { CurrencyBalance = ::CurrencyBalance, > + pallet_nomination_pools::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// The amount of balance slashed per each era that was wastefully checked. + /// + /// A reasonable value could be `runtime_weight_to_fee(weight_per_era_check)`. type SlashPerEra: Get>; + + /// The origin that can control this pallet. + type ControlOrigin: frame_support::traits::EnsureOrigin; + + /// The weight information of this pallet. + type WeightInfo: WeightInfo; } - /// One who wishes to be unstaked and join a pool. + /// An unstake request. #[derive(Encode, Decode, Eq, PartialEq, Clone, scale_info::TypeInfo)] - pub struct Unstake { + pub struct UnstakeRequest { /// Their stash account. stash: AccountId, /// The list of eras for which they have been checked. checked: Vec, - /// The pool they wish to join. - pool_id: PoolId, + /// The pool they wish to join, if any. + maybe_pool_id: Option, } /// The current "head of the queue" being unstaked. - /// The leading `Unstake` item due to be processed for unstaking. #[pallet::storage] #[pallet::unbounded] - pub type Head = StorageValue<_, Unstake, OptionQuery>; + pub type Head = StorageValue<_, UnstakeRequest, OptionQuery>; /// The map of all accounts wishing to be unstaked. - /// Points the `AccountId` wishing to unstake to the `PoolId` they wish to join thereafter. + /// + /// Points the `AccountId` wishing to unstake to the optional `PoolId` they wish to join + /// thereafter. #[pallet::storage] - pub type Queue = StorageMap<_, Twox64Concat, T::AccountId, PoolId>; + pub type Queue = StorageMap<_, Twox64Concat, T::AccountId, Option>; /// Number of eras to check per block. /// @@ -82,145 +129,253 @@ pub mod pallet { #[pallet::storage] pub type ErasToCheckPerBlock = StorageValue<_, u32, ValueQuery>; - /// TODO (TODISCUSS?): Could we introduce another storage item to streamline the checking of - /// exposure in eras? Aim: to speed up `is_exposed_in_era()`. - /// Could introduce `HistoricalNominationsEras` storage item that was mentioned here: - /// https://github.com/paritytech/substrate/issues/8436#issuecomment-1212962043 - /// Note: this would require us to append Eras when user `nominate`s (& rm Eras past - /// HISTORY_DEPTH) #[pallet::storage] - // pub type HistoricalNominatorEras = StorageMap<_, Twox64Concat, T::AccountId, - // Vec>; + /// The events of this pallet. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A staker was unstaked. + Unstaked { stash: T::AccountId, maybe_pool_id: Option, result: DispatchResult }, + /// A staker was slashed for requesting fast-unstake whilst being exposed. + Slashed { stash: T::AccountId, amount: BalanceOf }, + /// A staker was partially checked for the given eras, but the process did not finish. + Checked { stash: T::AccountId, eras: Vec }, + } #[pallet::hooks] impl Hooks for Pallet { fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight { - // We'll call `process_head` until 0 weight is returned - let mut remaining = remaining_weight; - loop { - // process head and - let last_consumed_weight = Self::process_head(remaining); - // if nothing was done, break loop - if last_consumed_weight == Weight::from(0 as u64) { - break - } - remaining = remaining.saturating_sub(last_consumed_weight); - } - remaining + Self::process_head(remaining_weight) } } #[pallet::call] impl Pallet { - /// enqueue oneself to be migrated from. + /// Register oneself for fast-unstake. + /// + /// The dispatch origin of this call must be signed by the controller account, similar to + /// `staking::unbond`. + /// + /// The stash associated with the origin must have no ongoing unlocking chunks. If + /// successful, this will fully unbond and chill the stash. Then, it will enqueue the stash + /// to be checked in further blocks. + /// + /// If by the time this is called, the stash is actually eligible for fast-unstake, then + /// they are guaranteed to remain eligible, because the call will chill them as well. + /// + /// If the check works, the entire staking data is removed, i.e. the stash is fully + /// unstaked, and they potentially join a pool with their entire bonded stake. + /// + /// If the check fails, the stash remains chilled and waiting for being unbonded as in with + /// the normal staking system, but they lose part of their unbonding chunks due to consuming + /// the chain's resources. + #[pallet::weight(0)] + pub fn register_fast_unstake( + origin: OriginFor, + maybe_pool_id: Option, + ) -> DispatchResult { + let ctrl = ensure_signed(origin)?; + + let ledger = pallet_staking::Ledger::::get(&ctrl).ok_or("NotController")?; + + ensure!(!Queue::::contains_key(&ledger.stash), "AlreadyQueued"); + ensure!( + Head::::get().map_or(true, |UnstakeRequest { stash, .. }| stash != ledger.stash), + "AlreadyHead" + ); + // second part of the && is defensive. + ensure!(ledger.active == ledger.total && ledger.unlocking.is_empty(), "NotFullyBonded"); + + // chill and fully unstake. + Staking::::chill(RawOrigin::Signed(ctrl.clone()).into())?; + Staking::::unbond(RawOrigin::Signed(ctrl).into(), ledger.total)?; + + // enqueue them. + Queue::::insert(ledger.stash, maybe_pool_id); + Ok(()) + } + + /// Deregister oneself from the fast-unstake (and possibly joining a pool). + /// + /// This is useful id one is registered, they are still waiting, and they change their mind. + /// + /// Note that the associated stash is still fully unbonded and chilled as a consequence of + /// calling `register_fast_unstake`. This should probably be followed by a call to + /// `Staking::rebond`. + #[pallet::weight(0)] + pub fn deregister(origin: OriginFor) -> DispatchResult { + let ctrl = ensure_signed(origin)?; + let stash = pallet_staking::Ledger::::get(&ctrl) + .map(|l| l.stash) + .ok_or("NotController")?; + ensure!(Queue::::contains_key(&stash), "NotQueued"); + ensure!( + Head::::get().map_or(true, |UnstakeRequest { stash, .. }| stash != stash), + "AlreadyHead" + ); + + Queue::::remove(stash); + Ok(()) + } + + /// Control the operation of this pallet. + /// + /// Dispatch origin must be signed by the [`Config::ControlOrigin`]. #[pallet::weight(0)] - pub fn enqueue(origin: OriginFor, pool_id: PoolId) -> DispatchResult { - todo!("assert not already in Queue."); - todo!("assert must be `bonded` (have actively bonded funds) to unstake."); - todo!("they must not already have any unbonding funds, i.e. ledger.unlocking"); - - // TODO: they should not be able to perform any actions in staking anymore once they are - // enqueued. This might be a bit nasty. Should use a custom signed extension. - let who = ensure_signed(origin)?; - todo!(); + pub fn control(origin: OriginFor, eras_to_check: EraIndex) -> DispatchResult { + let _ = T::ControlOrigin::ensure_origin(origin)?; + ErasToCheckPerBlock::::put(eras_to_check); + + Ok(()) } } impl Pallet { - /// Gets the first item of `Queue` or the `Head` Unstake entries if present. - /// Returns `None` if no entries present. - fn get_unstake_head() -> Option> { - Head::::take().or_else(|| { - Queue::::drain() - .take(1) - .map(|(stash, pool_id)| Unstake { stash, pool_id, checked: Default::default() }) - .next() - }) - } /// process up to `remaining_weight`. /// /// Returns the actual weight consumed. fn process_head(remaining_weight: Weight) -> Weight { let get_unstake_head_weight = T::DbWeight::get().reads(2); if remaining_weight < get_unstake_head_weight { + // check that we have enough weight o read // nothing can be done. return 0 } - let Unstake { stash, mut checked, pool_id } = match Self::get_unstake_head() { + if ::ElectionProvider::ongoing() { + // NOTE: we assume `ongoing` does not consume any weight. + // there is an ongoing election -- we better not do anything. Imagine someone is not + // exposed anywhere in the last era, and the snapshot for the election is already + // taken. In this time period, we don't want to accidentally unstake them. + return 0 + } + + let mut consumed_weight = 0; + let mut add_weight = + |amount: u64| consumed_weight = consumed_weight.saturating_add(amount); + + let UnstakeRequest { stash, mut checked, maybe_pool_id } = match Head::::take() + .or_else(|| { + Queue::::drain() + .take(1) + .map(|(stash, maybe_pool_id)| UnstakeRequest { + stash, + maybe_pool_id, + checked: Default::default(), + }) + .next() + }) { None => { // There's no `Head` and nothing in the `Queue`, nothing to do here. return get_unstake_head_weight }, - Some(x) => x, + Some(head) => { + add_weight(get_unstake_head_weight); + head + }, }; - // determine the amount of eras to check. - let weight_per_era_check: Weight = todo!("should come from our benchmarks"); - let max_eras_to_check = remaining_weight.div(weight_per_era_check); - let final_eras_to_check = ErasToCheckPerBlock::::get().min(max_eras_to_check as u32); + // determine the amount of eras to check. this is minimum of two criteria: + // `ErasToCheckPerBlock`, and how much weight is given to the on_idle hook. For the sake + // of simplicity, we assume we check at most one staker's eras per-block. + let final_eras_to_check = { + let weight_per_era_check: Weight = + ::WeightInfo::weight_per_era_check(); + let eras_to_check_weight_limit = remaining_weight.div(weight_per_era_check); + add_weight(T::DbWeight::get().reads(1)); + ErasToCheckPerBlock::::get().min(eras_to_check_weight_limit as u32) + }; - // return weight consumed if no eras to check (1 read). + // return weight consumed if no eras to check.. if final_eras_to_check.is_zero() { - return get_unstake_head_weight + T::DbWeight::get().reads(1) + return consumed_weight } - let slash_stash = |eras_checked: EraIndex| { - let slash_amount = T::SlashPerEra::get().saturating_mul(eras_checked.into()); - let (_, imbalance) = ::Currency::slash(&stash, slash_amount); + // the range that we're allowed to check in this round. + let current_era = pallet_staking::CurrentEra::::get().unwrap_or_default(); + let eras_to_check = { + let bonding_duration = ::BondingDuration::get(); + add_weight(T::DbWeight::get().reads(1)); + + // get the last available `bonding_duration` eras up to current era in reverse + // order. + let total_check_range = (current_era.saturating_sub(bonding_duration)..= + current_era) + .rev() + .collect::>(); + debug_assert!(total_check_range.len() <= bonding_duration as usize); + + // remove eras that have already been checked, take a maximum of + // final_eras_to_check. + total_check_range + .into_iter() + .filter(|e| !checked.contains(e)) + .take(final_eras_to_check as usize) + .collect::>() }; - let current_era = pallet_staking::CurrentEra::::get().unwrap_or_default(); - let bonding_duration = ::BondingDuration::get(); - - // get the last available `bonding_duration` eras up to current era in reverse order. - let total_check_range = (current_era.saturating_sub(bonding_duration)..current_era) - .rev() - .collect::>(); - - // remove eras that do not exist in `checked`. - let now_check_range = total_check_range - .iter() - .filter(|e| !checked.contains(e)) - .take(final_eras_to_check as usize) - .collect::>(); - - if now_check_range.is_empty() && final_eras_to_check > 0 { - // `stash` is not exposed in any era -- we can let go of them now. - let num_slashing_spans = 0; // TODO + if eras_to_check.is_empty() { + // `stash` is not exposed in any era now -- we can let go of them now. + let num_slashing_spans = Staking::::slashing_spans(&stash).iter().count() as u32; let ctrl = pallet_staking::Bonded::::get(&stash).unwrap(); let ledger = pallet_staking::Ledger::::get(ctrl).unwrap(); - pallet_staking::Pallet::::force_unstake( - frame_system::RawOrigin::Root.into(), + + add_weight(::WeightInfo::force_unstake( + num_slashing_spans, + )); + + let unstake_result = pallet_staking::Pallet::::force_unstake( + RawOrigin::Root.into(), stash.clone(), num_slashing_spans, - ) - .unwrap(); - pallet_nomination_pools::Pallet::::join( - frame_system::RawOrigin::Signed(stash.clone()).into(), - ledger.total, - pool_id, - ) - .unwrap(); - // TODO return weight, should be the weight of the code in this `if`. - // weight(nomination_pools.join) + weight(staking.force_unstake) + 2(read) - 0 + ); + let pool_stake_result = if let Some(pool_id) = maybe_pool_id { + add_weight(::WeightInfo::join()); + pallet_nomination_pools::Pallet::::join( + RawOrigin::Signed(stash.clone()).into(), + ledger.total, + pool_id, + ) + } else { + Ok(()) + }; + + let result = unstake_result.and(pool_stake_result); + Self::deposit_event(Event::::Unstaked { stash, maybe_pool_id, result }); } else { // eras remaining to be checked. - let is_exposed = now_check_range.iter().any(|e| Self::is_exposed_in_era(&stash, *e)); + let mut eras_checked = 0u32; + let is_exposed = eras_to_check.iter().any(|e| { + eras_checked.saturating_inc(); + Self::is_exposed_in_era(&stash, e) + }); + add_weight( + ::WeightInfo::weight_per_era_check() + .saturating_mul(eras_checked.into()), + ); + + // NOTE: you can be extremely unlucky and get slashed here: You are not exposed in + // the last 28 eras, have registered yourself to be unstaked, midway being checked, + // you are exposed. if is_exposed { - // this account was actually exposed in some era within the range -- slash them - // and remove them from the queue. - // TODO: slash - 0 // TODO: return weight, should be 'now_check_range.count() * - // weight_per_era_check + slash_weight' + let amount = T::SlashPerEra::get().saturating_mul(eras_checked.into()); + pallet_staking::slashing::do_slash::( + &stash, + amount, + &mut Default::default(), + &mut Default::default(), + current_era, + ); + add_weight(::WeightInfo::do_slash()); + Self::deposit_event(Event::::Slashed { stash, amount }); } else { // Not exposed in these two eras. - checked.extend(now_check_range); - Head::::put(Unstake { stash, checked, pool_id }); - 0 // TODO: return weight, should be 'now_check_range.count() * - // weight_per_era_check' + checked.extend(eras_to_check.clone()); + Head::::put(UnstakeRequest { stash: stash.clone(), checked, maybe_pool_id }); + Self::deposit_event(Event::::Checked { stash, eras: eras_to_check }); } } + + consumed_weight } /// Checks whether an account `who` has been exposed in an era. @@ -229,4 +384,70 @@ pub mod pallet { .any(|(_, exposures)| exposures.others.iter().any(|i| i.who == *who)) } } + + #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo, RuntimeDebugNoBound)] + #[scale_info(skip_type_params(T))] + pub struct PreventStakingOpsIfUnbonding( + sp_std::marker::PhantomData, + ); + + impl sp_runtime::traits::SignedExtension + for PreventStakingOpsIfUnbonding + where + ::Call: IsSubType>, + { + type AccountId = T::AccountId; + type Call = ::Call; + type AdditionalSigned = (); + type Pre = (); + const IDENTIFIER: &'static str = "PreventStakingOpsIfUnbonding"; + + fn additional_signed(&self) -> Result { + Ok(()) + } + + fn pre_dispatch( + self, + // NOTE: we want to prevent this stash-controller pair from doing anything in the + // staking system as long as they are registered here. `who` can be a stash or a + // controller. + stash_or_controller: &Self::AccountId, + call: &Self::Call, + _info: &sp_runtime::traits::DispatchInfoOf, + _len: usize, + ) -> Result { + // we don't check this in the tx-pool as it requires a storage read. + if >>::is_sub_type(call).is_some() { + let check_stash = |stash: &T::AccountId| { + if Queue::::contains_key(&stash) || + Head::::get().map_or(false, |u| &u.stash == stash) + { + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + } else { + Ok(()) + } + }; + match ( + pallet_staking::Ledger::::get(&stash_or_controller), + pallet_staking::Bonded::::get(&stash_or_controller), + ) { + (Some(ledger), None) => { + // it is a controller. + check_stash(&ledger.stash) + }, + (_, Some(_)) => { + // it is a stash. + let stash = stash_or_controller; + check_stash(stash) + }, + (None, None) => { + // They are not a staker -- let them execute. + Ok(()) + }, + } + } else { + Ok(()) + } + } + } } diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 0f659e12fb443..c1aef5f1c3739 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -119,82 +119,27 @@ pub fn new_test_ext() -> sp_io::TestExternalities { } #[test] -fn it_works_for_optional_value() { - new_test_ext().execute_with(|| { - // Check that GenesisBuilder works properly. - let val1 = 42; - let val2 = 27; - assert_eq!(Example::dummy(), Some(val1)); - - // Check that accumulate works when we have Some value in Dummy already. - assert_ok!(Example::accumulate_dummy(Origin::signed(1), val2)); - assert_eq!(Example::dummy(), Some(val1 + val2)); - - // Check that accumulate works when we Dummy has None in it. - >::on_initialize(2); - assert_ok!(Example::accumulate_dummy(Origin::signed(1), val1)); - assert_eq!(Example::dummy(), Some(val1 + val2 + val1)); - }); -} +fn cannot_register_if_in_queue() { -#[test] -fn it_works_for_default_value() { - new_test_ext().execute_with(|| { - assert_eq!(Example::foo(), 24); - assert_ok!(Example::accumulate_foo(Origin::signed(1), 1)); - assert_eq!(Example::foo(), 25); - }); } #[test] -fn set_dummy_works() { - new_test_ext().execute_with(|| { - let test_val = 133; - assert_ok!(Example::set_dummy(Origin::root(), test_val.into())); - assert_eq!(Example::dummy(), Some(test_val)); - }); +fn cannot_register_if_head() { + } #[test] -fn signed_ext_watch_dummy_works() { - new_test_ext().execute_with(|| { - let call = pallet_example_basic::Call::set_dummy { new_value: 10 }.into(); - let info = DispatchInfo::default(); - - assert_eq!( - WatchDummy::(PhantomData) - .validate(&1, &call, &info, 150) - .unwrap() - .priority, - u64::MAX, - ); - assert_eq!( - WatchDummy::(PhantomData).validate(&1, &call, &info, 250), - InvalidTransaction::ExhaustsResources.into(), - ); - }) +fn cannot_register_if_partially_unbonded() { + } #[test] -fn counted_map_works() { - new_test_ext().execute_with(|| { - assert_eq!(CountedMap::::count(), 0); - CountedMap::::insert(3, 3); - assert_eq!(CountedMap::::count(), 1); - }) +fn cannot_register_if_not_bonded() { + } + #[test] -fn weights_work() { - // must have a defined weight. - let default_call = pallet_example_basic::Call::::accumulate_dummy { increase_by: 10 }; - let info1 = default_call.get_dispatch_info(); - // aka. `let info = as GetDispatchInfo>::get_dispatch_info(&default_call);` - assert!(info1.weight > 0); - - // `set_dummy` is simpler than `accumulate_dummy`, and the weight - // should be less. - let custom_call = pallet_example_basic::Call::::set_dummy { new_value: 20 }; - let info2 = custom_call.get_dispatch_info(); - assert!(info1.weight > info2.weight); +fn unstake_paused_mid_election() { + todo!("a dude is being unstaked, midway being checked, election happens, they are still not exposed, but a new era needs to be checked, therefore this unstake takes longer than expected.") } diff --git a/frame/fast-unstake/src/weights.rs b/frame/fast-unstake/src/weights.rs deleted file mode 100644 index 5fc6434e396eb..0000000000000 --- a/frame/fast-unstake/src/weights.rs +++ /dev/null @@ -1,101 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 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_example_basic -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-03-15, STEPS: `[100, ]`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 - -// Executed Command: -// ./target/release/substrate -// benchmark -// --chain -// dev -// --execution -// wasm -// --wasm-execution -// compiled -// --pallet -// pallet_example_basic -// --extrinsic -// * -// --steps -// 100 -// --repeat -// 10 -// --raw -// --output -// ./ -// --template -// ./.maintain/frame-weight-template.hbs - - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for pallet_example_basic. -pub trait WeightInfo { - fn set_dummy_benchmark(b: u32, ) -> Weight; - fn accumulate_dummy(b: u32, ) -> Weight; - fn sort_vector(x: u32, ) -> Weight; -} - -/// Weights for pallet_example_basic using the Substrate node and recommended hardware. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - fn set_dummy_benchmark(b: u32, ) -> Weight { - (5_834_000 as Weight) - .saturating_add((24_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn accumulate_dummy(b: u32, ) -> Weight { - (51_353_000 as Weight) - .saturating_add((14_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn sort_vector(x: u32, ) -> Weight { - (2_569_000 as Weight) - // Standard Error: 0 - .saturating_add((4_000 as Weight).saturating_mul(x as Weight)) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - fn set_dummy_benchmark(b: u32, ) -> Weight { - (5_834_000 as Weight) - .saturating_add((24_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn accumulate_dummy(b: u32, ) -> Weight { - (51_353_000 as Weight) - .saturating_add((14_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn sort_vector(x: u32, ) -> Weight { - (2_569_000 as Weight) - // Standard Error: 0 - .saturating_add((4_000 as Weight).saturating_mul(x as Weight)) - } -} diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 1f11f0ff00ac1..ced2f9ff6abe9 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -463,6 +463,7 @@ pub mod pallet { /// Slashing spans for stash accounts. #[pallet::storage] + #[pallet::getter(fn slashing_spans)] pub(crate) type SlashingSpans = StorageMap<_, Twox64Concat, T::AccountId, slashing::SlashingSpans>; From 815a0e826260cb787e72683749371a2a140c4a98 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 22 Aug 2022 21:00:26 +0430 Subject: [PATCH 11/81] some notes --- frame/fast-unstake/src/lib.rs | 9 +++++++++ frame/fast-unstake/src/tests.rs | 18 ++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 8d87d0428e5bd..cc72197715b57 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -64,6 +64,11 @@ pub mod pallet { use sp_std::{ops::Div, vec::Vec}; pub trait WeightInfo { + fn register_fast_unstake() -> Weight; + fn deregister() -> Weight; + fn control() -> Weight; + + // TODO: maybe not needed. fn weight_per_era_check() -> Weight; fn do_slash() -> Weight; } @@ -241,6 +246,8 @@ pub mod pallet { return 0 } + // TODO ROSS: sum up all the weights consumed in the worse case execution of this code, if it is too much, early exit. + if ::ElectionProvider::ongoing() { // NOTE: we assume `ongoing` does not consume any weight. // there is an ongoing election -- we better not do anything. Imagine someone is not @@ -255,6 +262,7 @@ pub mod pallet { let UnstakeRequest { stash, mut checked, maybe_pool_id } = match Head::::take() .or_else(|| { + // TODO: this is purely unordered. If there is an attack, this could Queue::::drain() .take(1) .map(|(stash, maybe_pool_id)| UnstakeRequest { @@ -287,6 +295,7 @@ pub mod pallet { // return weight consumed if no eras to check.. if final_eras_to_check.is_zero() { + // TODO: if ErasToCheckPerBlock == 0 preferably don't consume any weight. return consumed_weight } diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index c1aef5f1c3739..1bf8df5cd9e9b 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -118,25 +118,19 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t.into() } -#[test] -fn cannot_register_if_in_queue() { - -} +// NOTE ROSS: Every error that can ve returned can be a test. #[test] -fn cannot_register_if_head() { - -} +fn cannot_register_if_in_queue() {} #[test] -fn cannot_register_if_partially_unbonded() { - -} +fn cannot_register_if_head() {} #[test] -fn cannot_register_if_not_bonded() { +fn cannot_register_if_has_unlocking_chunks() {} -} +#[test] +fn cannot_register_if_not_bonded() {} #[test] From 3747b54b37b2c7765b8f51a127e3e88f66da012c Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 23 Aug 2022 10:13:02 +0100 Subject: [PATCH 12/81] add mock boilerplate --- Cargo.lock | 6 + frame/fast-unstake/Cargo.toml | 9 ++ frame/fast-unstake/src/lib.rs | 8 +- frame/fast-unstake/src/mock.rs | 273 ++++++++++++++++++++++++++++++++ frame/fast-unstake/src/tests.rs | 114 ++----------- 5 files changed, 312 insertions(+), 98 deletions(-) create mode 100644 frame/fast-unstake/src/mock.rs diff --git a/Cargo.lock b/Cargo.lock index 886c584f4a113..3df372bff7a3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5690,8 +5690,12 @@ dependencies = [ "frame-support", "frame-system", "log", + "pallet-bags-list", + "pallet-balances", "pallet-nomination-pools", "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", "parity-scale-codec", "scale-info", "sp-core", @@ -5699,6 +5703,8 @@ dependencies = [ "sp-runtime", "sp-staking", "sp-std", + "sp-tracing", + "substrate-test-utils", ] [[package]] diff --git a/frame/fast-unstake/Cargo.toml b/frame/fast-unstake/Cargo.toml index eb5971b5ee836..6b5dce52dfe03 100644 --- a/frame/fast-unstake/Cargo.toml +++ b/frame/fast-unstake/Cargo.toml @@ -25,6 +25,9 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primit sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-staking = { default-features = false, path = "../../primitives/staking" } +pallet-bags-list = { default-features = false, path = "../bags-list" } +pallet-balances = { default-features = false, path = "../balances" } +pallet-timestamp = { default-features = false, path = "../timestamp" } pallet-staking = { default-features = false, path = "../staking" } pallet-nomination-pools = { default-features = false, path = "../nomination-pools" } frame-election-provider-support = { default-features = false, path = "../election-provider-support" } @@ -32,7 +35,10 @@ frame-election-provider-support = { default-features = false, path = "../electio frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } [dev-dependencies] +pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } +sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } [features] default = ["std"] @@ -49,8 +55,11 @@ std = [ "sp-runtime/std", "sp-std/std", + "pallet-bags-list/std", "pallet-staking/std", "pallet-nomination-pools/std", + "pallet-balances/std", + "pallet-timestamp/std", "frame-election-provider-support/std", "frame-benchmarking/std", diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index cc72197715b57..be5fb9009cd05 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -42,6 +42,11 @@ pub use pallet::*; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + #[frame_support::pallet] pub mod pallet { use frame_support::pallet_prelude::*; @@ -246,7 +251,8 @@ pub mod pallet { return 0 } - // TODO ROSS: sum up all the weights consumed in the worse case execution of this code, if it is too much, early exit. + // TODO ROSS: sum up all the weights consumed in the worse case execution of this code, + // if it is too much, early exit. if ::ElectionProvider::ongoing() { // NOTE: we assume `ongoing` does not consume any weight. diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs new file mode 100644 index 0000000000000..34488bfc9e2c8 --- /dev/null +++ b/frame/fast-unstake/src/mock.rs @@ -0,0 +1,273 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2022 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. + +use crate::{self as pallet_fast_unstake, *}; +use frame_election_provider_support::VoteWeight; +use frame_support::{ + assert_ok, + pallet_prelude::*, + parameter_types, + traits::{ConstU64, ConstU8}, + PalletId, +}; +use sp_runtime::{ + traits::{Convert, IdentityLookup}, + FixedU128, +}; + +type AccountId = u128; +type AccountIndex = u32; +type BlockNumber = u64; +type Balance = u128; + +pub(crate) type T = Runtime; + +// pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128; +// pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128; + +// parameter_types! { +// pub BlockWeights: frame_system::limits::BlockWeights = +// frame_system::limits::BlockWeights::simple_max(1024); +// } + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Index = AccountIndex; + type BlockNumber = BlockNumber; + type Call = Call; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = sp_runtime::testing::Header; + type Event = Event; + type BlockHashCount = (); + 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_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<5>; + type WeightInfo = (); +} + +parameter_types! { + pub static ExistentialDeposit: Balance = 1; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +pallet_staking_reward_curve::build! { + const I_NPOS: sp_runtime::curve::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 RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; + pub static BondingDuration: u32 = 3; +} + +impl pallet_staking::Config for Runtime { + type MaxNominations = ConstU32<16>; + type Currency = Balances; + type CurrencyBalance = Balance; + type UnixTime = pallet_timestamp::Pallet; + type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type RewardRemainder = (); + type Event = Event; + type Slash = (); + type Reward = (); + type SessionsPerEra = (); + type SlashDeferDuration = (); + type SlashCancelOrigin = frame_system::EnsureRoot; + type BondingDuration = BondingDuration; + type SessionInterface = (); + type EraPayout = pallet_staking::ConvertCurve; + type NextNewSession = (); + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = (); + type ElectionProvider = + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = pallet_bags_list::Pallet; + type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = Pools; + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type WeightInfo = (); +} + +parameter_types! { + pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; +} + +impl pallet_bags_list::Config for Runtime { + type Event = Event; + type WeightInfo = (); + type BagThresholds = BagThresholds; + type ScoreProvider = Staking; + type Score = VoteWeight; +} + +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> sp_core::U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub const PostUnbondingPoolsWindow: u32 = 10; + pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); +} + +impl pallet_nomination_pools::Config for Runtime { + type Event = Event; + type WeightInfo = (); + type Currency = Balances; + type CurrencyBalance = Balance; + type RewardCounter = FixedU128; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type StakingInterface = Staking; + type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; + type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = ConstU32<8>; + type MaxPointsToBalance = ConstU8<10>; + type PalletId = PoolsPalletId; +} + +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, + BagsList: pallet_bags_list::{Pallet, Call, Storage, Event}, + Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let _ = pallet_nomination_pools::GenesisConfig:: { + min_join_bond: 2, + min_create_bond: 2, + max_pools: Some(3), + max_members_per_pool: Some(5), + max_members: Some(3 * 5), + } + .assimilate_storage(&mut storage) + .unwrap(); + + let _ = pallet_balances::GenesisConfig:: { + balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)], + } + .assimilate_storage(&mut storage) + .unwrap(); + + let mut ext = sp_io::TestExternalities::from(storage); + + ext.execute_with(|| { + // for events to be deposited. + frame_system::Pallet::::set_block_number(1); + + // set some limit for nominations. + assert_ok!(Staking::set_staking_configs( + Origin::root(), + pallet_staking::ConfigOp::Set(10), // minimum nominator bond + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + )); + }); + + ext +} + +parameter_types! { + static ObservedEventsPools: usize = 0; + static ObservedEventsStaking: usize = 0; + static ObservedEventsBalances: usize = 0; +} + +pub(crate) fn pool_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::Pools(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = ObservedEventsPools::get(); + ObservedEventsPools::set(events.len()); + events.into_iter().skip(already_seen).collect() +} + +pub(crate) fn staking_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::Staking(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = ObservedEventsStaking::get(); + ObservedEventsStaking::set(events.len()); + events.into_iter().skip(already_seen).collect() +} diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 1bf8df5cd9e9b..bff5b68d906e8 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -17,106 +17,27 @@ //! Tests for pallet-example-basic. -use crate::*; +use crate::mock::*; +use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support}; use frame_support::{ - assert_ok, parameter_types, - traits::{ConstU64, OnInitialize}, - weights::{DispatchInfo, GetDispatchInfo}, + assert_noop, assert_ok, assert_storage_noop, bounded_vec, + dispatch::WithPostDispatchInfo, + pallet_prelude::*, + traits::{Currency, Get, ReservableCurrency}, + weights::{extract_actual_weight, GetDispatchInfo}, }; -use sp_core::H256; -// The testing primitives are very useful for avoiding having to work with signatures -// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. +use pallet_balances::Error as BalancesError; use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + assert_eq_error_rate, + traits::{BadOrigin, Dispatchable}, + Perbill, Percent, }; -// Reexport crate as its pallet name for construct_runtime. -use crate as pallet_example_basic; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -// For testing the pallet, we construct a mock runtime. -frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Example: pallet_example_basic::{Pallet, Call, Storage, Config, Event}, - } -); - -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); -} -impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type Origin = Origin; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Call = Call; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = 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 = (); - type MaxReserves = (); - type ReserveIdentifier = [u8; 8]; - type Balance = u64; - type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ConstU64<1>; - type AccountStore = System; - type WeightInfo = (); -} - -impl Config for Test { - type MagicNumber = ConstU64<1_000_000_000>; - type Event = Event; - type WeightInfo = (); -} - -// This function basically just builds a genesis storage key/value store according to -// our desired mockup. -pub fn new_test_ext() -> sp_io::TestExternalities { - let t = GenesisConfig { - // We use default for brevity, but you can configure as desired if needed. - system: Default::default(), - balances: Default::default(), - example: pallet_example_basic::GenesisConfig { - dummy: 42, - // we configure the map with (key, value) pairs. - bar: vec![(1, 2), (2, 3)], - foo: 24, - }, - } - .build_storage() - .unwrap(); - t.into() -} +use sp_staking::{ + offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, + SessionIndex, +}; +use sp_std::prelude::*; +use substrate_test_utils::assert_eq_uvec; // NOTE ROSS: Every error that can ve returned can be a test. @@ -132,7 +53,6 @@ fn cannot_register_if_has_unlocking_chunks() {} #[test] fn cannot_register_if_not_bonded() {} - #[test] fn unstake_paused_mid_election() { todo!("a dude is being unstaked, midway being checked, election happens, they are still not exposed, but a new era needs to be checked, therefore this unstake takes longer than expected.") From 17fda1743a3f4ade4ed8a248dabd91425f3b000e Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 23 Aug 2022 10:17:32 +0100 Subject: [PATCH 13/81] more boilerplate --- frame/fast-unstake/src/mock.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 34488bfc9e2c8..0372754f85f34 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -34,19 +34,14 @@ type AccountIndex = u32; type BlockNumber = u64; type Balance = u128; -pub(crate) type T = Runtime; - -// pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128; -// pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128; - -// parameter_types! { -// pub BlockWeights: frame_system::limits::BlockWeights = -// frame_system::limits::BlockWeights::simple_max(1024); -// } +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(1024); +} impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); + type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = (); type Origin = Origin; @@ -195,12 +190,12 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: frame_system::{Pallet, Call, Event}, + System: frame_system::{Pallet, Call, Event}, Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, - BagsList: pallet_bags_list::{Pallet, Call, Storage, Event}, - Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, + BagsList: pallet_bags_list::{Pallet, Call, Storage, Event}, + Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, } ); From 078ad676228778681a6954e1bad50f003cc3d730 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 23 Aug 2022 13:51:03 +0430 Subject: [PATCH 14/81] simplify the weight stuff --- frame/fast-unstake/Cargo.toml | 2 +- frame/fast-unstake/src/benchmarking.rs | 68 +++++----------- frame/fast-unstake/src/lib.rs | 105 +++++++++++++++---------- 3 files changed, 86 insertions(+), 89 deletions(-) diff --git a/frame/fast-unstake/Cargo.toml b/frame/fast-unstake/Cargo.toml index eb5971b5ee836..e4e7c88404a27 100644 --- a/frame/fast-unstake/Cargo.toml +++ b/frame/fast-unstake/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" license = "Unlicense" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -description = "FRAME example pallet" +description = "FRAME fast unstake pallet" readme = "README.md" [package.metadata.docs.rs] diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index d7b933577ead5..bd31f43aa57c1 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -23,56 +23,28 @@ use crate::*; use frame_benchmarking::{benchmarks, whitelisted_caller}; use frame_system::RawOrigin; -// To actually run this benchmark on pallet-example-basic, we need to put this pallet into the -// runtime and compile it with `runtime-benchmarks` feature. The detail procedures are -// documented at: -// https://docs.substrate.io/v3/runtime/benchmarking#how-to-benchmark -// -// The auto-generated weight estimate of this pallet is copied over to the `weights.rs` file. -// The exact command of how the estimate generated is printed at the top of the file. - -// Details on using the benchmarks macro can be seen at: -// https://paritytech.github.io/substrate/master/frame_benchmarking/trait.Benchmarking.html#tymethod.benchmarks benchmarks! { - // This will measure the execution time of `set_dummy` for b in [1..1000] range. - set_dummy_benchmark { - // This is the benchmark setup phase - let b in 1 .. 1000; - }: set_dummy(RawOrigin::Root, b.into()) // The execution phase is just running `set_dummy` extrinsic call - verify { - // This is the optional benchmark verification phase, asserting certain states. - assert_eq!(Pallet::::dummy(), Some(b.into())) - } + // on_idle, we we don't check anyone, but fully unbond and move them to another pool. + on_idle_empty {} + : {} + verify {} + + // on_idle, when we check some number of eras, + on_idle_check {} + : {} + verify {} + + register_fast_unstake {} + : {} + verify {} - // This will measure the execution time of `accumulate_dummy` for b in [1..1000] range. - // The benchmark execution phase is shorthanded. When the name of the benchmark case is the same - // as the extrinsic call. `_(...)` is used to represent the extrinsic name. - // The benchmark verification phase is omitted. - accumulate_dummy { - let b in 1 .. 1000; - // The caller account is whitelisted for DB reads/write by the benchmarking macro. - let caller: T::AccountId = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), b.into()) + deregister {} + : {} + verify {} - // This will measure the execution time of sorting a vector. - sort_vector { - let x in 0 .. 10000; - let mut m = Vec::::new(); - for i in (0..x).rev() { - m.push(i); - } - }: { - // The benchmark execution phase could also be a closure with custom code - m.sort_unstable(); - } + control {} + : {} + verify {} - // This line generates test cases for benchmarking, and could be run by: - // `cargo test -p pallet-example-basic --all-features`, you will see one line per case: - // `test benchmarking::bench_sort_vector ... ok` - // `test benchmarking::bench_accumulate_dummy ... ok` - // `test benchmarking::bench_set_dummy_benchmark ... ok` in the result. - // - // The line generates three steps per benchmark, with repeat=1 and the three steps are - // [low, mid, high] of the range. - impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test) + // impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test) } diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index cc72197715b57..c43f89470bd79 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -42,8 +42,22 @@ pub use pallet::*; +pub const LOG_TARGET: &'static str = "runtime::fast-unstake"; + +// 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)* + ) + }; +} + #[frame_support::pallet] pub mod pallet { + use super::*; use frame_support::pallet_prelude::*; use frame_system::{pallet_prelude::*, RawOrigin}; use sp_std::prelude::*; @@ -68,9 +82,8 @@ pub mod pallet { fn deregister() -> Weight; fn control() -> Weight; - // TODO: maybe not needed. - fn weight_per_era_check() -> Weight; - fn do_slash() -> Weight; + fn on_idle_empty() -> Weight; + fn on_idle_check(e: u32) -> Weight; } type BalanceOf = <::Currency as Currency< @@ -144,6 +157,9 @@ pub mod pallet { Slashed { stash: T::AccountId, amount: BalanceOf }, /// A staker was partially checked for the given eras, but the process did not finish. Checked { stash: T::AccountId, eras: Vec }, + /// Some internal error happened while migrating stash. They are removed as head as a + /// consequence. + Errored { stash: T::AccountId }, } #[pallet::hooks] @@ -181,6 +197,7 @@ pub mod pallet { let ctrl = ensure_signed(origin)?; let ledger = pallet_staking::Ledger::::get(&ctrl).ok_or("NotController")?; + ensure!(pallet_staking::Nominators::::contains_key(&ledger.stash), "NotNominator"); ensure!(!Queue::::contains_key(&ledger.stash), "AlreadyQueued"); ensure!( @@ -238,31 +255,36 @@ pub mod pallet { /// process up to `remaining_weight`. /// /// Returns the actual weight consumed. + /// + /// Written for readability in mind, not efficiency. For example: + /// + /// 1. We assume this is only ever called once per `on_idle`. This is because we know that + /// in all use cases, even a single nominator cannot be unbonded in a single call. Multiple + /// calls to this function are thus not needed. 2. We will only mark a staker fn process_head(remaining_weight: Weight) -> Weight { - let get_unstake_head_weight = T::DbWeight::get().reads(2); - if remaining_weight < get_unstake_head_weight { - // check that we have enough weight o read - // nothing can be done. - return 0 + let eras_to_check_per_block = ErasToCheckPerBlock::::get(); + if eras_to_check_per_block.is_zero() { + return T::DbWeight::get().reads(1) } - // TODO ROSS: sum up all the weights consumed in the worse case execution of this code, if it is too much, early exit. + let total_worse_case_weight = + ::WeightInfo::on_idle_check(eras_to_check_per_block) + .max(::WeightInfo::on_idle_empty()); + if total_worse_case_weight > remaining_weight { + log!(warn, "total_worse_case_weight > remaining_weight, early exiting"); + return T::DbWeight::get().reads(1) + } if ::ElectionProvider::ongoing() { // NOTE: we assume `ongoing` does not consume any weight. // there is an ongoing election -- we better not do anything. Imagine someone is not // exposed anywhere in the last era, and the snapshot for the election is already // taken. In this time period, we don't want to accidentally unstake them. - return 0 + return T::DbWeight::get().reads(1) } - let mut consumed_weight = 0; - let mut add_weight = - |amount: u64| consumed_weight = consumed_weight.saturating_add(amount); - let UnstakeRequest { stash, mut checked, maybe_pool_id } = match Head::::take() .or_else(|| { - // TODO: this is purely unordered. If there is an attack, this could Queue::::drain() .take(1) .map(|(stash, maybe_pool_id)| UnstakeRequest { @@ -274,36 +296,32 @@ pub mod pallet { }) { None => { // There's no `Head` and nothing in the `Queue`, nothing to do here. - return get_unstake_head_weight - }, - Some(head) => { - add_weight(get_unstake_head_weight); - head + return T::DbWeight::get().reads_writes(2, 1) }, + Some(head) => head, }; // determine the amount of eras to check. this is minimum of two criteria: // `ErasToCheckPerBlock`, and how much weight is given to the on_idle hook. For the sake // of simplicity, we assume we check at most one staker's eras per-block. let final_eras_to_check = { - let weight_per_era_check: Weight = - ::WeightInfo::weight_per_era_check(); + // NOTE: here we're assuming that the number of validators has only ever increased, + // meaning that the number of exposures to check is either this per era, or less. + let weight_per_era_check = T::DbWeight::get().reads(1) * + pallet_staking::ValidatorCount::::get() as Weight; let eras_to_check_weight_limit = remaining_weight.div(weight_per_era_check); - add_weight(T::DbWeight::get().reads(1)); - ErasToCheckPerBlock::::get().min(eras_to_check_weight_limit as u32) + eras_to_check_per_block.min(eras_to_check_weight_limit as u32) }; // return weight consumed if no eras to check.. if final_eras_to_check.is_zero() { - // TODO: if ErasToCheckPerBlock == 0 preferably don't consume any weight. - return consumed_weight + return T::DbWeight::get().reads_writes(2, 1) } // the range that we're allowed to check in this round. let current_era = pallet_staking::CurrentEra::::get().unwrap_or_default(); let eras_to_check = { let bonding_duration = ::BondingDuration::get(); - add_weight(T::DbWeight::get().reads(1)); // get the last available `bonding_duration` eras up to current era in reverse // order. @@ -325,20 +343,30 @@ pub mod pallet { if eras_to_check.is_empty() { // `stash` is not exposed in any era now -- we can let go of them now. let num_slashing_spans = Staking::::slashing_spans(&stash).iter().count() as u32; - let ctrl = pallet_staking::Bonded::::get(&stash).unwrap(); - let ledger = pallet_staking::Ledger::::get(ctrl).unwrap(); - add_weight(::WeightInfo::force_unstake( - num_slashing_spans, - )); + let ctrl = match pallet_staking::Bonded::::get(&stash) { + Some(ctrl) => ctrl, + None => { + Self::deposit_event(Event::::Errored { stash }); + return ::WeightInfo::on_idle_empty() + }, + }; + + let ledger = match pallet_staking::Ledger::::get(ctrl) { + Some(ledger) => ledger, + None => { + Self::deposit_event(Event::::Errored { stash }); + return ::WeightInfo::on_idle_empty() + }, + }; let unstake_result = pallet_staking::Pallet::::force_unstake( RawOrigin::Root.into(), stash.clone(), num_slashing_spans, ); + let pool_stake_result = if let Some(pool_id) = maybe_pool_id { - add_weight(::WeightInfo::join()); pallet_nomination_pools::Pallet::::join( RawOrigin::Signed(stash.clone()).into(), ledger.total, @@ -350,6 +378,8 @@ pub mod pallet { let result = unstake_result.and(pool_stake_result); Self::deposit_event(Event::::Unstaked { stash, maybe_pool_id, result }); + + ::WeightInfo::on_idle_empty() } else { // eras remaining to be checked. let mut eras_checked = 0u32; @@ -357,10 +387,6 @@ pub mod pallet { eras_checked.saturating_inc(); Self::is_exposed_in_era(&stash, e) }); - add_weight( - ::WeightInfo::weight_per_era_check() - .saturating_mul(eras_checked.into()), - ); // NOTE: you can be extremely unlucky and get slashed here: You are not exposed in // the last 28 eras, have registered yourself to be unstaked, midway being checked, @@ -374,7 +400,6 @@ pub mod pallet { &mut Default::default(), current_era, ); - add_weight(::WeightInfo::do_slash()); Self::deposit_event(Event::::Slashed { stash, amount }); } else { // Not exposed in these two eras. @@ -382,9 +407,9 @@ pub mod pallet { Head::::put(UnstakeRequest { stash: stash.clone(), checked, maybe_pool_id }); Self::deposit_event(Event::::Checked { stash, eras: eras_to_check }); } - } - consumed_weight + ::WeightInfo::on_idle_check(final_eras_to_check) + } } /// Checks whether an account `who` has been exposed in an era. From 18e8fa3202b9b8de7390b8252c4edac4bffa48d9 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 23 Aug 2022 11:39:49 +0100 Subject: [PATCH 15/81] ExtBuilder for pools --- frame/fast-unstake/src/mock.rs | 129 ++++++++++++++++++++++++++++++-- frame/fast-unstake/src/tests.rs | 62 ++++++++++++++- 2 files changed, 180 insertions(+), 11 deletions(-) diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 0372754f85f34..aefaf4234123b 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -15,13 +15,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::*; use crate::{self as pallet_fast_unstake, *}; use frame_election_provider_support::VoteWeight; use frame_support::{ assert_ok, pallet_prelude::*, parameter_types, - traits::{ConstU64, ConstU8}, + traits::{ConstU64, ConstU8, Currency}, PalletId, }; use sp_runtime::{ @@ -29,10 +30,14 @@ use sp_runtime::{ FixedU128, }; -type AccountId = u128; -type AccountIndex = u32; -type BlockNumber = u64; -type Balance = u128; +use frame_system::RawOrigin; +use pallet_nomination_pools::{LastPoolId, PoolId, *}; +use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, ops::Div, vec::Vec}; + +pub type AccountId = u128; +pub type AccountIndex = u32; +pub type BlockNumber = u64; +pub type Balance = u128; parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = @@ -74,7 +79,7 @@ impl pallet_timestamp::Config for Runtime { } parameter_types! { - pub static ExistentialDeposit: Balance = 1; + pub static ExistentialDeposit: Balance = 5; } impl pallet_balances::Config for Runtime { @@ -103,6 +108,14 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; pub static BondingDuration: u32 = 3; + pub static MinJoinBondConfig: Balance = 2; + pub static CurrentEra: u32 = 0; + pub storage BondedBalanceMap: BTreeMap = Default::default(); + pub storage UnbondingBalanceMap: BTreeMap = Default::default(); + #[derive(Clone, PartialEq)] + pub static MaxUnbonding: u32 = 8; + pub static StakingMinBond: Balance = 10; + pub storage Nominations: Option> = None; } impl pallet_staking::Config for Runtime { @@ -163,6 +176,8 @@ impl Convert for U256ToBalance { parameter_types! { pub const PostUnbondingPoolsWindow: u32 = 10; pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); + pub static MaxMetadataLen: u32 = 2; + pub static CheckLevel: u8 = 255; } impl pallet_nomination_pools::Config for Runtime { @@ -175,7 +190,7 @@ impl pallet_nomination_pools::Config for Runtime { type U256ToBalance = U256ToBalance; type StakingInterface = Staking; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; - type MaxMetadataLen = ConstU32<256>; + type MaxMetadataLen = MaxMetadataLen; type MaxUnbonding = ConstU32<8>; type MaxPointsToBalance = ConstU8<10>; type PalletId = PoolsPalletId; @@ -266,3 +281,103 @@ pub(crate) fn staking_events_since_last_call() -> Vec, + max_members: Option, + max_members_per_pool: Option, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { members: Default::default(), max_members: Some(4), max_members_per_pool: Some(3) } + } +} + +impl ExtBuilder { + // Add members to pool 0. + pub(crate) fn add_members(mut self, members: Vec<(AccountId, Balance)>) -> Self { + self.members = members; + self + } + + pub(crate) fn ed(self, ed: Balance) -> Self { + ExistentialDeposit::set(ed); + self + } + + pub(crate) fn min_bond(self, min: Balance) -> Self { + StakingMinBond::set(min); + self + } + + pub(crate) fn min_join_bond(self, min: Balance) -> Self { + MinJoinBondConfig::set(min); + self + } + + pub(crate) fn with_check(self, level: u8) -> Self { + CheckLevel::set(level); + self + } + + pub(crate) fn max_members(mut self, max: Option) -> Self { + self.max_members = max; + self + } + + pub(crate) fn max_members_per_pool(mut self, max: Option) -> Self { + self.max_members_per_pool = max; + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let _ = pallet_nomination_pools::GenesisConfig:: { + min_join_bond: MinJoinBondConfig::get(), + min_create_bond: 2, + max_pools: Some(2), + max_members_per_pool: self.max_members_per_pool, + max_members: self.max_members, + } + .assimilate_storage(&mut storage); + + let mut ext = sp_io::TestExternalities::from(storage); + + ext.execute_with(|| { + // for events to be deposited. + frame_system::Pallet::::set_block_number(1); + + // make a pool + let amount_to_bond = MinJoinBondConfig::get(); + Balances::make_free_balance_be(&10, amount_to_bond * 5); + assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902)); + + let last_pool = LastPoolId::::get(); + for (account_id, bonded) in self.members { + Balances::make_free_balance_be(&account_id, bonded * 2); + assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, last_pool)); + } + }); + + ext + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + self.build().execute_with(|| { + test(); + Pools::sanity_checks(CheckLevel::get()).unwrap(); + }) + } +} + +pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) { + BondedPools::::try_mutate(pool_id, |maybe_bonded_pool| { + maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { + bonded_pool.state = state; + }) + }) + .unwrap() +} diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index bff5b68d906e8..e11f05c1f91c6 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -17,7 +17,8 @@ //! Tests for pallet-example-basic. -use crate::mock::*; +use super::*; +use crate::{mock::*, Event}; use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support}; use frame_support::{ assert_noop, assert_ok, assert_storage_noop, bounded_vec, @@ -27,19 +28,72 @@ use frame_support::{ weights::{extract_actual_weight, GetDispatchInfo}, }; use pallet_balances::Error as BalancesError; +use pallet_nomination_pools::{BondedPool, PoolId, *}; + use sp_runtime::{ assert_eq_error_rate, - traits::{BadOrigin, Dispatchable}, + traits::{BadOrigin, Dispatchable, Zero}, Perbill, Percent, }; use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - SessionIndex, + SessionIndex, StakingInterface, }; use sp_std::prelude::*; use substrate_test_utils::assert_eq_uvec; -// NOTE ROSS: Every error that can ve returned can be a test. +pub const DEFAULT_ROLES: PoolRoles = + PoolRoles { depositor: 10, root: Some(900), nominator: Some(901), state_toggler: Some(902) }; + +#[test] +fn test_setup_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(BondedPools::::count(), 1); + assert_eq!(RewardPools::::count(), 1); + assert_eq!(SubPoolsStorage::::count(), 0); + assert_eq!(PoolMembers::::count(), 1); + assert_eq!(Staking::bonding_duration(), 3); + + let last_pool = LastPoolId::::get(); + assert_eq!( + BondedPool::::get(last_pool).unwrap(), + BondedPool:: { + id: last_pool, + inner: BondedPoolInner { + state: PoolState::Open, + points: 10, + member_counter: 1, + roles: DEFAULT_ROLES + }, + } + ); + assert_eq!( + RewardPools::::get(last_pool).unwrap(), + RewardPool:: { + last_recorded_reward_counter: Zero::zero(), + last_recorded_total_payouts: 0, + total_rewards_claimed: 0 + } + ); + assert_eq!( + PoolMembers::::get(10).unwrap(), + PoolMember:: { pool_id: last_pool, points: 10, ..Default::default() } + ); + + let bonded_account = Pools::create_bonded_account(last_pool); + let reward_account = Pools::create_reward_account(last_pool); + + // the bonded_account should be bonded by the depositor's funds. + assert_eq!(Staking::active_stake(&bonded_account).unwrap(), 10); + assert_eq!(Staking::total_stake(&bonded_account).unwrap(), 10); + + // but not nominating yet. + assert!(Nominations::get().is_none()); + + // reward account should have an initial ED in it. + assert_eq!(Balances::free_balance(&reward_account), Balances::minimum_balance()); + }) +} #[test] fn cannot_register_if_in_queue() {} From c232118a0336773193e8add445cce6907977afa3 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 23 Aug 2022 11:43:09 +0100 Subject: [PATCH 16/81] fmt --- frame/fast-unstake/src/mock.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index aefaf4234123b..6cfabaf18bf69 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -333,7 +333,8 @@ impl ExtBuilder { pub(crate) fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); - let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut storage = + frame_system::GenesisConfig::default().build_storage::().unwrap(); let _ = pallet_nomination_pools::GenesisConfig:: { min_join_bond: MinJoinBondConfig::get(), From 427d2c9ba80aa56ef8ed0d5f0719fa1f5236e65f Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 23 Aug 2022 12:08:07 +0100 Subject: [PATCH 17/81] rm bags-list, simplify setup_works --- Cargo.lock | 1 - frame/fast-unstake/Cargo.toml | 2 -- frame/fast-unstake/src/mock.rs | 17 ++------------ frame/fast-unstake/src/tests.rs | 39 +-------------------------------- 4 files changed, 3 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3df372bff7a3a..a64c28b3db024 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5690,7 +5690,6 @@ dependencies = [ "frame-support", "frame-system", "log", - "pallet-bags-list", "pallet-balances", "pallet-nomination-pools", "pallet-staking", diff --git a/frame/fast-unstake/Cargo.toml b/frame/fast-unstake/Cargo.toml index 9389839cbcac4..b0b7ae7cad116 100644 --- a/frame/fast-unstake/Cargo.toml +++ b/frame/fast-unstake/Cargo.toml @@ -25,7 +25,6 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primit sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-staking = { default-features = false, path = "../../primitives/staking" } -pallet-bags-list = { default-features = false, path = "../bags-list" } pallet-balances = { default-features = false, path = "../balances" } pallet-timestamp = { default-features = false, path = "../timestamp" } pallet-staking = { default-features = false, path = "../staking" } @@ -55,7 +54,6 @@ std = [ "sp-runtime/std", "sp-std/std", - "pallet-bags-list/std", "pallet-staking/std", "pallet-nomination-pools/std", "pallet-balances/std", diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 6cfabaf18bf69..02f57f0439b9e 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -79,7 +79,7 @@ impl pallet_timestamp::Config for Runtime { } parameter_types! { - pub static ExistentialDeposit: Balance = 5; + pub static ExistentialDeposit: Balance = 1; } impl pallet_balances::Config for Runtime { @@ -140,25 +140,13 @@ impl pallet_staking::Config for Runtime { type ElectionProvider = frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; type GenesisElectionProvider = Self::ElectionProvider; - type VoterList = pallet_bags_list::Pallet; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } -parameter_types! { - pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; -} - -impl pallet_bags_list::Config for Runtime { - type Event = Event; - type WeightInfo = (); - type BagThresholds = BagThresholds; - type ScoreProvider = Staking; - type Score = VoteWeight; -} - pub struct BalanceToU256; impl Convert for BalanceToU256 { fn convert(n: Balance) -> sp_core::U256 { @@ -209,7 +197,6 @@ frame_support::construct_runtime!( Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, - BagsList: pallet_bags_list::{Pallet, Call, Storage, Event}, Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, } ); diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index e11f05c1f91c6..fbeea5c01948b 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -50,48 +50,11 @@ fn test_setup_works() { ExtBuilder::default().build_and_execute(|| { assert_eq!(BondedPools::::count(), 1); assert_eq!(RewardPools::::count(), 1); - assert_eq!(SubPoolsStorage::::count(), 0); - assert_eq!(PoolMembers::::count(), 1); assert_eq!(Staking::bonding_duration(), 3); let last_pool = LastPoolId::::get(); - assert_eq!( - BondedPool::::get(last_pool).unwrap(), - BondedPool:: { - id: last_pool, - inner: BondedPoolInner { - state: PoolState::Open, - points: 10, - member_counter: 1, - roles: DEFAULT_ROLES - }, - } - ); - assert_eq!( - RewardPools::::get(last_pool).unwrap(), - RewardPool:: { - last_recorded_reward_counter: Zero::zero(), - last_recorded_total_payouts: 0, - total_rewards_claimed: 0 - } - ); - assert_eq!( - PoolMembers::::get(10).unwrap(), - PoolMember:: { pool_id: last_pool, points: 10, ..Default::default() } - ); - let bonded_account = Pools::create_bonded_account(last_pool); - let reward_account = Pools::create_reward_account(last_pool); - - // the bonded_account should be bonded by the depositor's funds. - assert_eq!(Staking::active_stake(&bonded_account).unwrap(), 10); - assert_eq!(Staking::total_stake(&bonded_account).unwrap(), 10); - - // but not nominating yet. - assert!(Nominations::get().is_none()); - - // reward account should have an initial ED in it. - assert_eq!(Balances::free_balance(&reward_account), Balances::minimum_balance()); + assert_eq!(last_pool, 1); }) } From 4cbe28a9761af3a81f3ff697a36c9d1f7f1c3881 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 23 Aug 2022 13:44:20 +0100 Subject: [PATCH 18/81] mock + tests boilerplate --- frame/fast-unstake/src/mock.rs | 36 +++++++++++++++++++++++++++++---- frame/fast-unstake/src/tests.rs | 17 ++++++++++++++-- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 02f57f0439b9e..d268480c1ffc5 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -16,8 +16,7 @@ // limitations under the License. use super::*; -use crate::{self as pallet_fast_unstake, *}; -use frame_election_provider_support::VoteWeight; +use crate::{self as fast_unstake}; use frame_support::{ assert_ok, pallet_prelude::*, @@ -32,7 +31,7 @@ use sp_runtime::{ use frame_system::RawOrigin; use pallet_nomination_pools::{LastPoolId, PoolId, *}; -use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, ops::Div, vec::Vec}; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; pub type AccountId = u128; pub type AccountIndex = u32; @@ -184,9 +183,38 @@ impl pallet_nomination_pools::Config for Runtime { type PalletId = PoolsPalletId; } +struct FastUnstakeWeightInfo; +impl fast_unstake::WeightInfo for FastUnstakeWeightInfo { + fn register_fast_unstake() -> Weight { + 10 + } + fn deregister() -> Weight { + 5 + } + fn control() -> Weight { + 2 + } + fn on_idle_empty() -> Weight { + 1 + } + fn on_idle_check(e: u32) -> Weight { + 10 + } +} + +parameter_types! { + pub static SlashPerEra: u32 = 100; +} + +impl fast_unstake::Config for Runtime { + type Event = Event; + type SlashPerEra = SlashPerEra; + type ControlOrigin = Origin; + type WeightInfo = FastUnstakeWeightInfo; +} + type Block = frame_system::mocking::MockBlock; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; - frame_support::construct_runtime!( pub enum Runtime where Block = Block, diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index fbeea5c01948b..e8f1858c46118 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -29,6 +29,7 @@ use frame_support::{ }; use pallet_balances::Error as BalancesError; use pallet_nomination_pools::{BondedPool, PoolId, *}; +use pallet_staking::RewardDestination; use sp_runtime::{ assert_eq_error_rate, @@ -51,9 +52,7 @@ fn test_setup_works() { assert_eq!(BondedPools::::count(), 1); assert_eq!(RewardPools::::count(), 1); assert_eq!(Staking::bonding_duration(), 3); - let last_pool = LastPoolId::::get(); - assert_eq!(last_pool, 1); }) } @@ -70,6 +69,20 @@ fn cannot_register_if_has_unlocking_chunks() {} #[test] fn cannot_register_if_not_bonded() {} +#[test] +fn register_should_work() { + // mint accounts 1-5 with 200 units of token + for i in 1..5 { + let _ = Balances::make_free_balance_be(&i, 2000); + } + // account 1 bond (stash) + // account 2: controller + // bond 100 tokens + // reward destination to controller account + assert_ok!(Staking::bond(Origin::signed(1), 2, 100, RewardDestination::Controller)); + // TODO: register for unstake. +} + #[test] fn unstake_paused_mid_election() { todo!("a dude is being unstaked, midway being checked, election happens, they are still not exposed, but a new era needs to be checked, therefore this unstake takes longer than expected.") From c5df0a7470d1e3dd7067ee6a6f7883b957803d0d Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 23 Aug 2022 17:20:15 +0430 Subject: [PATCH 19/81] make some benchmarks work --- Cargo.lock | 1 + bin/node/runtime/Cargo.toml | 4 + bin/node/runtime/src/lib.rs | 9 + .../solution-type/src/single_page.rs | 1 + frame/election-provider-support/src/traits.rs | 4 + frame/fast-unstake/Cargo.toml | 6 +- frame/fast-unstake/src/benchmarking.rs | 161 ++++++++++++++++-- frame/fast-unstake/src/lib.rs | 48 ++++-- .../nomination-pools/benchmarking/src/lib.rs | 36 ++-- frame/nomination-pools/src/lib.rs | 2 +- frame/staking/src/pallet/impls.rs | 4 +- 11 files changed, 229 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 886c584f4a113..17af1611fa218 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3355,6 +3355,7 @@ dependencies = [ "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", + "pallet-fast-unstake", "pallet-gilt", "pallet-grandpa", "pallet-identity", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 10b15b6ec554d..b1cc6a089ab82 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -68,6 +68,7 @@ pallet-democracy = { version = "4.0.0-dev", default-features = false, path = ".. pallet-election-provider-multi-phase = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-multi-phase" } pallet-election-provider-support-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-support/benchmarking", optional = true } pallet-elections-phragmen = { version = "5.0.0-dev", default-features = false, path = "../../../frame/elections-phragmen" } +pallet-fast-unstake = { version = "4.0.0-dev", default-features = false, path = "../../../frame/fast-unstake" } pallet-gilt = { version = "4.0.0-dev", default-features = false, path = "../../../frame/gilt" } pallet-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../../frame/grandpa" } pallet-im-online = { version = "4.0.0-dev", default-features = false, path = "../../../frame/im-online" } @@ -135,6 +136,7 @@ std = [ "pallet-conviction-voting/std", "pallet-democracy/std", "pallet-elections-phragmen/std", + "pallet-fast-unstake/std", "frame-executive/std", "pallet-gilt/std", "pallet-grandpa/std", @@ -211,6 +213,7 @@ runtime-benchmarks = [ "pallet-election-provider-multi-phase/runtime-benchmarks", "pallet-election-provider-support-benchmarking/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", + "pallet-fast-unstake/runtime-benchmarks", "pallet-gilt/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", "pallet-identity/runtime-benchmarks", @@ -262,6 +265,7 @@ try-runtime = [ "pallet-democracy/try-runtime", "pallet-election-provider-multi-phase/try-runtime", "pallet-elections-phragmen/try-runtime", + "pallet-fast-unstake/try-runtime", "pallet-gilt/try-runtime", "pallet-grandpa/try-runtime", "pallet-identity/try-runtime", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 76ed3310cefbf..03def0b95506b 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -571,6 +571,13 @@ impl pallet_staking::Config for Runtime { type BenchmarkingConfig = StakingBenchmarkingConfig; } +impl pallet_fast_unstake::Config for Runtime { + type Event = Event; + type SlashPerEra = ConstU128<{ DOLLARS }>; + type ControlOrigin = frame_system::EnsureRoot; + type WeightInfo = (); +} + parameter_types! { // phase durations. 1/4 of the last session for each. pub const SignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; @@ -1641,6 +1648,7 @@ construct_runtime!( NominationPools: pallet_nomination_pools, RankedPolls: pallet_referenda::, RankedCollective: pallet_ranked_collective, + FastUnstake: pallet_fast_unstake, } ); @@ -1716,6 +1724,7 @@ mod benches { [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] [pallet_election_provider_support_benchmarking, EPSBench::] [pallet_elections_phragmen, Elections] + [pallet_fast_unstake, FastUnstake] [pallet_gilt, Gilt] [pallet_grandpa, Grandpa] [pallet_identity, Identity] diff --git a/frame/election-provider-support/solution-type/src/single_page.rs b/frame/election-provider-support/solution-type/src/single_page.rs index a7ccf5085d2b1..aee60e93afbb9 100644 --- a/frame/election-provider-support/solution-type/src/single_page.rs +++ b/frame/election-provider-support/solution-type/src/single_page.rs @@ -178,6 +178,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { all_targets.into_iter().collect() } + } type __IndexAssignment = _feps::IndexAssignment< diff --git a/frame/election-provider-support/src/traits.rs b/frame/election-provider-support/src/traits.rs index ed812e2e0f2c4..46e0d521b3604 100644 --- a/frame/election-provider-support/src/traits.rs +++ b/frame/election-provider-support/src/traits.rs @@ -122,4 +122,8 @@ where voter_at: impl Fn(Self::VoterIndex) -> Option, target_at: impl Fn(Self::TargetIndex) -> Option, ) -> Result>, Error>; + + /// Build a fake version of self with the given number of voters and targets. + #[cfg(feature = "srd")] + fn fake_from(v: u32, t: u32) -> Self; } diff --git a/frame/fast-unstake/Cargo.toml b/frame/fast-unstake/Cargo.toml index e4e7c88404a27..2318912a57b37 100644 --- a/frame/fast-unstake/Cargo.toml +++ b/frame/fast-unstake/Cargo.toml @@ -55,5 +55,9 @@ std = [ "frame-benchmarking/std", ] -runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-staking/runtime-benchmarks", +] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index bd31f43aa57c1..e9bf8e5e90568 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -19,31 +19,172 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::*; -use frame_benchmarking::{benchmarks, whitelisted_caller}; +use crate::{Pallet as FastUnstake, *}; +use frame_benchmarking::{benchmarks, whitelist_account}; +use frame_support::traits::{Currency, EnsureOrigin, Get}; use frame_system::RawOrigin; +use pallet_nomination_pools::{Pallet as Pools, PoolId}; +use pallet_staking::Pallet as Staking; +use sp_runtime::traits::{Bounded, StaticLookup, Zero}; +use sp_std::prelude::*; +use frame_support::traits::Hooks; + +const USER_SEED: u32 = 0; +const DEFAULT_BACKER_PER_VALIDATOR: u32 = 128; + +type CurrencyOf = ::Currency; + +fn l( + who: T::AccountId, +) -> <::Lookup as StaticLookup>::Source { + T::Lookup::unlookup(who) +} + +fn create_unexposed_nominator() -> T::AccountId { + let account = frame_benchmarking::account::("nominator_42", 0, USER_SEED); + let stake = CurrencyOf::::minimum_balance() * 100u32.into(); + CurrencyOf::::make_free_balance_be(&account, stake * 10u32.into()); + + let account_lookup = l::(account.clone()); + // bond and nominate ourselves, this will guarantee that we are not backing anyone. + Staking::::bond( + RawOrigin::Signed(account.clone()).into(), + account_lookup.clone(), + stake, + pallet_staking::RewardDestination::Controller, + ) + .unwrap(); + Staking::::nominate(RawOrigin::Signed(account.clone()).into(), vec![account_lookup]) + .unwrap(); + + account +} + +fn setup_pool() -> PoolId { + let depositor = frame_benchmarking::account::("depositor_42", 0, USER_SEED); + let depositor_lookup = l::(depositor.clone()); + + let stake = Pools::::depositor_min_bond(); + CurrencyOf::::make_free_balance_be(&depositor, stake * 10u32.into()); + + Pools::::create( + RawOrigin::Signed(depositor.clone()).into(), + stake, + depositor_lookup.clone(), + depositor_lookup.clone(), + depositor_lookup, + ) + .unwrap(); + + pallet_nomination_pools::LastPoolId::::get() +} + +fn setup_staking(v: u32) { + let ed = CurrencyOf::::minimum_balance(); + + // make sure there are enough validator candidates + // NOTE: seems like these actually don't need to be real validators.. + for seed in 0..(pallet_staking::Validators::::iter().count().saturating_sub(v as usize)) { + let account = + frame_benchmarking::account::("validator", seed as u32, USER_SEED); + let account_lookup = l::(account.clone()); + + let stake = ed * 100u32.into(); + CurrencyOf::::make_free_balance_be(&account, stake * 10u32.into()); + + Staking::::bond( + RawOrigin::Signed(account.clone()).into(), + account_lookup.clone(), + stake, + pallet_staking::RewardDestination::Controller, + ) + .unwrap(); + Staking::::validate(RawOrigin::Signed(account.clone()).into(), Default::default()) + .unwrap(); + } + + let validators = pallet_staking::Validators::::iter_keys().collect::>(); + + for era in 0..(2 * ::BondingDuration::get()) { + let others = (0..DEFAULT_BACKER_PER_VALIDATOR) + .map(|s| { + let who = frame_benchmarking::account::("nominator", era, s); + let value = ed; + pallet_staking::IndividualExposure { who, value } + }) + .collect::>(); + let exposure = + pallet_staking::Exposure { total: Default::default(), own: Default::default(), others }; + validators.iter().for_each(|v| { + Staking::::add_era_stakers(era, v.clone(), exposure.clone()); + }); + } +} + +fn on_idle_full_block() { + let remaining_weight = ::BlockWeights::get().max_block; + FastUnstake::::on_idle(Zero::zero(), remaining_weight); +} benchmarks! { // on_idle, we we don't check anyone, but fully unbond and move them to another pool. - on_idle_empty {} - : {} + on_idle_unstake { + let who = create_unexposed_nominator::(); + let pool_id = setup_pool::(); + FastUnstake::::register_fast_unstake( + RawOrigin::Signed(who.clone()).into(), + Some(pool_id) + ).unwrap(); + ErasToCheckPerBlock::::put(1); + + // no era to check, because staking era is still 0. + assert_eq!(pallet_staking::CurrentEra::::get().unwrap_or_default(), 0); + + // run on_idle once. + on_idle_full_block::(); + } + : { + on_idle_full_block::(); + } verify {} // on_idle, when we check some number of eras, - on_idle_check {} + on_idle_check { + // staking should have enough validators and progress to a state where enough eras exist. + // add non-exposed nominator + // register + // + } : {} verify {} - register_fast_unstake {} + // same as above, but we do the entire check and realize that we had to slash our nominator now. + on_idle_check_slash {} : {} verify {} - deregister {} - : {} + register_fast_unstake { + let who = create_unexposed_nominator::(); + whitelist_account!(who); + } + :_(RawOrigin::Signed(who.clone()), None) verify {} - control {} - : {} + deregister { + let who = create_unexposed_nominator::(); + FastUnstake::::register_fast_unstake( + RawOrigin::Signed(who.clone()).into(), + None + ).unwrap(); + whitelist_account!(who); + } + :_(RawOrigin::Signed(who.clone())) + verify {} + + control { + let origin = ::ControlOrigin::successful_origin(); + } + : _(origin, 128) verify {} // impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index c43f89470bd79..67716e1c5d7c0 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -40,6 +40,9 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + pub use pallet::*; pub const LOG_TARGET: &'static str = "runtime::fast-unstake"; @@ -72,8 +75,7 @@ pub mod pallet { use sp_staking::EraIndex; use frame_election_provider_support::ElectionProvider; - use pallet_nomination_pools::WeightInfo as _; - use pallet_staking::{Pallet as Staking, WeightInfo as _}; + use pallet_staking::Pallet as Staking; use sp_std::{ops::Div, vec::Vec}; @@ -82,10 +84,28 @@ pub mod pallet { fn deregister() -> Weight; fn control() -> Weight; - fn on_idle_empty() -> Weight; + fn on_idle_unstake() -> Weight; fn on_idle_check(e: u32) -> Weight; } + impl WeightInfo for () { + fn register_fast_unstake() -> Weight { + 0 + } + fn deregister() -> Weight { + 0 + } + fn control() -> Weight { + 0 + } + fn on_idle_unstake() -> Weight { + 0 + } + fn on_idle_check(_: u32) -> Weight { + 0 + } + } + type BalanceOf = <::Currency as Currency< ::AccountId, >>::Balance; @@ -189,7 +209,7 @@ pub mod pallet { /// If the check fails, the stash remains chilled and waiting for being unbonded as in with /// the normal staking system, but they lose part of their unbonding chunks due to consuming /// the chain's resources. - #[pallet::weight(0)] + #[pallet::weight(::WeightInfo::register_fast_unstake())] pub fn register_fast_unstake( origin: OriginFor, maybe_pool_id: Option, @@ -223,7 +243,7 @@ pub mod pallet { /// Note that the associated stash is still fully unbonded and chilled as a consequence of /// calling `register_fast_unstake`. This should probably be followed by a call to /// `Staking::rebond`. - #[pallet::weight(0)] + #[pallet::weight(::WeightInfo::deregister())] pub fn deregister(origin: OriginFor) -> DispatchResult { let ctrl = ensure_signed(origin)?; let stash = pallet_staking::Ledger::::get(&ctrl) @@ -242,7 +262,7 @@ pub mod pallet { /// Control the operation of this pallet. /// /// Dispatch origin must be signed by the [`Config::ControlOrigin`]. - #[pallet::weight(0)] + #[pallet::weight(::WeightInfo::control())] pub fn control(origin: OriginFor, eras_to_check: EraIndex) -> DispatchResult { let _ = T::ControlOrigin::ensure_origin(origin)?; ErasToCheckPerBlock::::put(eras_to_check); @@ -267,9 +287,13 @@ pub mod pallet { return T::DbWeight::get().reads(1) } - let total_worse_case_weight = - ::WeightInfo::on_idle_check(eras_to_check_per_block) - .max(::WeightInfo::on_idle_empty()); + #[cfg(feature = "runtime-benchmarks")] + let total_worse_case_weight = ::WeightInfo::on_idle_check(eras_to_check_per_block) + .max(::WeightInfo::on_idle_unstake()); + // TODO: not sure if this is needed. + #[cfg(not(feature = "runtime-benchmarks"))] + let total_worse_case_weight = 0; + if total_worse_case_weight > remaining_weight { log!(warn, "total_worse_case_weight > remaining_weight, early exiting"); return T::DbWeight::get().reads(1) @@ -348,7 +372,7 @@ pub mod pallet { Some(ctrl) => ctrl, None => { Self::deposit_event(Event::::Errored { stash }); - return ::WeightInfo::on_idle_empty() + return ::WeightInfo::on_idle_unstake() }, }; @@ -356,7 +380,7 @@ pub mod pallet { Some(ledger) => ledger, None => { Self::deposit_event(Event::::Errored { stash }); - return ::WeightInfo::on_idle_empty() + return ::WeightInfo::on_idle_unstake() }, }; @@ -379,7 +403,7 @@ pub mod pallet { let result = unstake_result.and(pool_stake_result); Self::deposit_event(Event::::Unstaked { stash, maybe_pool_id, result }); - ::WeightInfo::on_idle_empty() + ::WeightInfo::on_idle_unstake() } else { // eras remaining to be checked. let mut eras_checked = 0u32; diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index f4ecb4a9b0ff1..fd4ecf994da23 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -48,12 +48,6 @@ pub trait Config: pub struct Pallet(Pools); -fn min_create_bond() -> BalanceOf { - MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()) -} - fn create_funded_user_with_balance( string: &'static str, n: u32, @@ -216,7 +210,7 @@ impl ListScenario { frame_benchmarking::benchmarks! { join { - let origin_weight = min_create_bond::() * 2u32.into(); + let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); // setup the worst case list scenario. let scenario = ListScenario::::new(origin_weight, true)?; @@ -242,7 +236,7 @@ frame_benchmarking::benchmarks! { } bond_extra_transfer { - let origin_weight = min_create_bond::() * 2u32.into(); + let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); let scenario = ListScenario::::new(origin_weight, true)?; let extra = scenario.dest_weight - origin_weight; @@ -257,7 +251,7 @@ frame_benchmarking::benchmarks! { } bond_extra_reward { - let origin_weight = min_create_bond::() * 2u32.into(); + let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); let scenario = ListScenario::::new(origin_weight, true)?; let extra = (scenario.dest_weight - origin_weight).max(CurrencyOf::::minimum_balance()); @@ -275,7 +269,7 @@ frame_benchmarking::benchmarks! { } claim_payout { - let origin_weight = min_create_bond::() * 2u32.into(); + let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); let ed = CurrencyOf::::minimum_balance(); let (depositor, pool_account) = create_pool_account::(0, origin_weight); let reward_account = Pools::::create_reward_account(1); @@ -305,7 +299,7 @@ frame_benchmarking::benchmarks! { unbond { // 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 = min_create_bond::() * 200u32.into(); + let origin_weight = Pools::::depositor_min_bond() * 200u32.into(); let scenario = ListScenario::::new(origin_weight, false)?; let amount = origin_weight - scenario.dest_weight; @@ -336,7 +330,7 @@ frame_benchmarking::benchmarks! { pool_withdraw_unbonded { let s in 0 .. MAX_SPANS; - let min_create_bond = min_create_bond::(); + let min_create_bond = Pools::::depositor_min_bond(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Add a new member @@ -378,7 +372,7 @@ frame_benchmarking::benchmarks! { withdraw_unbonded_update { let s in 0 .. MAX_SPANS; - let min_create_bond = min_create_bond::(); + let min_create_bond = Pools::::depositor_min_bond(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Add a new member @@ -424,7 +418,7 @@ frame_benchmarking::benchmarks! { withdraw_unbonded_kill { let s in 0 .. MAX_SPANS; - let min_create_bond = min_create_bond::(); + let min_create_bond = Pools::::depositor_min_bond(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); let depositor_lookup = T::Lookup::unlookup(depositor.clone()); @@ -489,14 +483,14 @@ frame_benchmarking::benchmarks! { } create { - let min_create_bond = min_create_bond::(); + let min_create_bond = Pools::::depositor_min_bond(); let depositor: T::AccountId = account("depositor", USER_SEED, 0); let depositor_lookup = T::Lookup::unlookup(depositor.clone()); // Give the depositor some balance to bond CurrencyOf::::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); - // Make sure no pools exist as a pre-condition for our verify checks + // Make sure no Pools exist aT a pre-condition for our verify checks assert_eq!(RewardPools::::count(), 0); assert_eq!(BondedPools::::count(), 0); @@ -536,7 +530,7 @@ frame_benchmarking::benchmarks! { let n in 1 .. T::MaxNominations::get(); // Create a pool - let min_create_bond = min_create_bond::() * 2u32.into(); + let min_create_bond = Pools::::depositor_min_bond() * 2u32.into(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Create some accounts to nominate. For the sake of benchmarking they don't need to be @@ -573,7 +567,7 @@ frame_benchmarking::benchmarks! { set_state { // Create a pool - let min_create_bond = min_create_bond::(); + let min_create_bond = Pools::::depositor_min_bond(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); BondedPools::::mutate(&1, |maybe_pool| { // Force the pool into an invalid state @@ -591,7 +585,7 @@ frame_benchmarking::benchmarks! { let n in 1 .. ::MaxMetadataLen::get(); // Create a pool - let (depositor, pool_account) = create_pool_account::(0, min_create_bond::() * 2u32.into()); + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into()); // Create metadata of the max possible size let metadata: Vec = (0..n).map(|_| 42).collect(); @@ -620,7 +614,7 @@ frame_benchmarking::benchmarks! { update_roles { let first_id = pallet_nomination_pools::LastPoolId::::get() + 1; - let (root, _) = create_pool_account::(0, min_create_bond::() * 2u32.into()); + let (root, _) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into()); let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED); }:_( Origin::Signed(root.clone()), @@ -642,7 +636,7 @@ frame_benchmarking::benchmarks! { chill { // Create a pool - let (depositor, pool_account) = create_pool_account::(0, min_create_bond::() * 2u32.into()); + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into()); // Nominate with the pool. let validators: Vec<_> = (0..T::MaxNominations::get()) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 62d0b3ddd55cb..764f0038ced02 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -2175,7 +2175,7 @@ impl Pallet { /// /// It is essentially `max { MinNominatorBond, MinCreateBond, MinJoinBond }`, where the former /// is coming from the staking pallet and the latter two are configured in this pallet. - fn depositor_min_bond() -> BalanceOf { + pub fn depositor_min_bond() -> BalanceOf { T::StakingInterface::minimum_bond() .max(MinCreateBond::::get()) .max(MinJoinBond::::get()) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 68aa97db8a324..625241510b85b 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -645,10 +645,10 @@ impl Pallet { #[cfg(feature = "runtime-benchmarks")] pub fn add_era_stakers( current_era: EraIndex, - controller: T::AccountId, + stash: T::AccountId, exposure: Exposure>, ) { - >::insert(¤t_era, &controller, &exposure); + >::insert(¤t_era, &stash, &exposure); } #[cfg(feature = "runtime-benchmarks")] From 7f784d5d40c61fad991f5d6ee7f587a3627e178a Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 23 Aug 2022 13:51:46 +0100 Subject: [PATCH 20/81] mock boilerplate --- frame/fast-unstake/src/mock.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index d268480c1ffc5..5080853f82c81 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -183,7 +183,7 @@ impl pallet_nomination_pools::Config for Runtime { type PalletId = PoolsPalletId; } -struct FastUnstakeWeightInfo; +pub struct FastUnstakeWeightInfo; impl fast_unstake::WeightInfo for FastUnstakeWeightInfo { fn register_fast_unstake() -> Weight { 10 @@ -209,7 +209,7 @@ parameter_types! { impl fast_unstake::Config for Runtime { type Event = Event; type SlashPerEra = SlashPerEra; - type ControlOrigin = Origin; + type ControlOrigin = frame_system::EnsureRoot; type WeightInfo = FastUnstakeWeightInfo; } @@ -226,6 +226,7 @@ frame_support::construct_runtime!( Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, + FastUnstake: fast_unstake::{Pallet, Call, Storage, Event}, } ); From 754f189640473971da9f71f0bf3efa7fe4bcebb0 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 23 Aug 2022 14:05:03 +0100 Subject: [PATCH 21/81] tests boilerplate --- frame/fast-unstake/src/tests.rs | 46 ++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index e8f1858c46118..8a14f004e58b4 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -58,32 +58,48 @@ fn test_setup_works() { } #[test] -fn cannot_register_if_in_queue() {} +fn cannot_register_if_in_queue() { + new_test_ext().execute_with(|| {}); +} #[test] -fn cannot_register_if_head() {} +fn cannot_register_if_head() { + new_test_ext().execute_with(|| {}); +} #[test] -fn cannot_register_if_has_unlocking_chunks() {} +fn cannot_register_if_has_unlocking_chunks() { + new_test_ext().execute_with(|| {}); +} #[test] -fn cannot_register_if_not_bonded() {} +fn cannot_register_if_not_bonded() { + new_test_ext().execute_with(|| {}); +} #[test] fn register_should_work() { - // mint accounts 1-5 with 200 units of token - for i in 1..5 { - let _ = Balances::make_free_balance_be(&i, 2000); - } - // account 1 bond (stash) - // account 2: controller - // bond 100 tokens - // reward destination to controller account - assert_ok!(Staking::bond(Origin::signed(1), 2, 100, RewardDestination::Controller)); - // TODO: register for unstake. + new_test_ext().execute_with(|| { + // mint accounts 1-2 with 200 units of token + for i in 1..2 { + let _ = Balances::make_free_balance_be(&i, 200); + } + // account 1 bond (stash) + // account 2: controller + // bond 100 tokens + // reward destination to controller account + assert_ok!(Staking::bond(Origin::signed(1), 2, 100, RewardDestination::Controller)); + // TODO: register for unstake + }); } #[test] fn unstake_paused_mid_election() { - todo!("a dude is being unstaked, midway being checked, election happens, they are still not exposed, but a new era needs to be checked, therefore this unstake takes longer than expected.") + new_test_ext().execute_with(|| { + todo!( + "a dude is being unstaked, midway being checked, election happens, they are still not + exposed, but a new era needs to be checked, therefore this unstake takes longer than + expected." + ) + }); } From 0c69897962148b7efc46dbce5a649a1f9b377d3c Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 23 Aug 2022 14:18:28 +0100 Subject: [PATCH 22/81] run_to_block works --- frame/fast-unstake/src/benchmarking.rs | 3 +-- frame/fast-unstake/src/mock.rs | 21 ++++++++++++++++++--- frame/fast-unstake/src/tests.rs | 13 ++++++++++++- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index e9bf8e5e90568..b27a9561fdd58 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -21,13 +21,12 @@ use crate::{Pallet as FastUnstake, *}; use frame_benchmarking::{benchmarks, whitelist_account}; -use frame_support::traits::{Currency, EnsureOrigin, Get}; +use frame_support::traits::{Currency, EnsureOrigin, Get, Hooks}; use frame_system::RawOrigin; use pallet_nomination_pools::{Pallet as Pools, PoolId}; use pallet_staking::Pallet as Staking; use sp_runtime::traits::{Bounded, StaticLookup, Zero}; use sp_std::prelude::*; -use frame_support::traits::Hooks; const USER_SEED: u32 = 0; const DEFAULT_BACKER_PER_VALIDATOR: u32 = 128; diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 5080853f82c81..734be35158275 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -194,8 +194,8 @@ impl fast_unstake::WeightInfo for FastUnstakeWeightInfo { fn control() -> Weight { 2 } - fn on_idle_empty() -> Weight { - 1 + fn on_idle_unstake() -> Weight { + 10 } fn on_idle_check(e: u32) -> Weight { 10 @@ -378,7 +378,6 @@ impl ExtBuilder { assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, last_pool)); } }); - ext } @@ -398,3 +397,19 @@ pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) { }) .unwrap() } + +pub(crate) fn run_to_block(n: u64) { + let current_block = System::block_number(); + assert!(n > current_block); + while System::block_number() < n { + Balances::on_finalize(System::block_number()); + Staking::on_finalize(System::block_number()); + Pools::on_finalize(System::block_number()); + FastUnstake::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + Balances::on_initialize(System::block_number()); + Staking::on_initialize(System::block_number()); + Pools::on_initialize(System::block_number()); + FastUnstake::on_initialize(System::block_number()); + } +} diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 8a14f004e58b4..2c0018aa8788c 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -78,12 +78,13 @@ fn cannot_register_if_not_bonded() { } #[test] -fn register_should_work() { +fn register_works() { new_test_ext().execute_with(|| { // mint accounts 1-2 with 200 units of token for i in 1..2 { let _ = Balances::make_free_balance_be(&i, 200); } + run_to_block(2); // account 1 bond (stash) // account 2: controller // bond 100 tokens @@ -93,6 +94,16 @@ fn register_should_work() { }); } +#[test] +fn deregister_works() { + new_test_ext().execute_with(|| {}); +} + +#[test] +fn control_works() { + new_test_ext().execute_with(|| {}); +} + #[test] fn unstake_paused_mid_election() { new_test_ext().execute_with(|| { From bbd0be685981464c76d687002219ffa1ad55f8ca Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 23 Aug 2022 17:42:05 +0100 Subject: [PATCH 23/81] add Error enums --- frame/fast-unstake/src/lib.rs | 60 +++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 57a37c4e00309..3cc9e1ba7d8ec 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -66,29 +66,26 @@ macro_rules! log { #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_election_provider_support::ElectionProvider; + use frame_support::{ + pallet_prelude::*, + traits::{Currency, IsSubType}, + }; use frame_system::{pallet_prelude::*, RawOrigin}; - use sp_std::prelude::*; - - use frame_support::traits::{Currency, IsSubType}; use pallet_nomination_pools::PoolId; + use pallet_staking::Pallet as Staking; use sp_runtime::{ traits::{Saturating, Zero}, transaction_validity::{InvalidTransaction, TransactionValidityError}, DispatchResult, }; use sp_staking::EraIndex; - - use frame_election_provider_support::ElectionProvider; - use pallet_staking::Pallet as Staking; - - use sp_std::{ops::Div, vec::Vec}; + use sp_std::{ops::Div, prelude::*, vec::Vec}; pub trait WeightInfo { fn register_fast_unstake() -> Weight; fn deregister() -> Weight; fn control() -> Weight; - fn on_idle_unstake() -> Weight; fn on_idle_check(e: u32) -> Weight; } @@ -187,6 +184,23 @@ pub mod pallet { Errored { stash: T::AccountId }, } + #[pallet::error] + #[cfg_attr(test, derive(PartialEq))] + pub enum Error { + /// The provided Controller account was not found. + NotController, + /// The nominator was not found. + NotNominator, + /// The bonded account has already been queued. + AlreadyQueued, + /// The bonded account has active unlocking chunks. + NotFullyBonded, + /// The provided unstaker is not in the `Queue`. + NotQueued, + /// The provided unstaker is already in Head, and cannot deregister. + AlreadyHead, + } + #[pallet::hooks] impl Hooks for Pallet { fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight { @@ -201,7 +215,7 @@ pub mod pallet { /// The dispatch origin of this call must be signed by the controller account, similar to /// `staking::unbond`. /// - /// The stash associated with the origin must have no ongoing unlocking chunks. If + /// The stash associated with the origin must have no ongoing unlocking chunks. If /// successful, this will fully unbond and chill the stash. Then, it will enqueue the stash /// to be checked in further blocks. /// @@ -221,16 +235,22 @@ pub mod pallet { ) -> DispatchResult { let ctrl = ensure_signed(origin)?; - let ledger = pallet_staking::Ledger::::get(&ctrl).ok_or("NotController")?; - ensure!(pallet_staking::Nominators::::contains_key(&ledger.stash), "NotNominator"); - - ensure!(!Queue::::contains_key(&ledger.stash), "AlreadyQueued"); + let ledger = + pallet_staking::Ledger::::get(&ctrl).ok_or(Error::::NotController)?; + ensure!( + pallet_staking::Nominators::::contains_key(&ledger.stash), + Error::::NotNominator + ); + ensure!(!Queue::::contains_key(&ledger.stash), Error::::AlreadyQueued); ensure!( Head::::get().map_or(true, |UnstakeRequest { stash, .. }| stash != ledger.stash), - "AlreadyHead" + Error::::AlreadyHead ); // second part of the && is defensive. - ensure!(ledger.active == ledger.total && ledger.unlocking.is_empty(), "NotFullyBonded"); + ensure!( + ledger.active == ledger.total && ledger.unlocking.is_empty(), + Error::::NotFullyBonded + ); // chill and fully unstake. Staking::::chill(RawOrigin::Signed(ctrl.clone()).into())?; @@ -253,11 +273,11 @@ pub mod pallet { let ctrl = ensure_signed(origin)?; let stash = pallet_staking::Ledger::::get(&ctrl) .map(|l| l.stash) - .ok_or("NotController")?; - ensure!(Queue::::contains_key(&stash), "NotQueued"); + .ok_or(Error::::NotController)?; + ensure!(Queue::::contains_key(&stash), Error::::NotQueued); ensure!( Head::::get().map_or(true, |UnstakeRequest { stash, .. }| stash != stash), - "AlreadyHead" + Error::::AlreadyHead ); Queue::::remove(stash); From b350f150dbfcefc0dbdbc2dd5f6cc251547853fd Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 23 Aug 2022 17:43:24 +0100 Subject: [PATCH 24/81] add test --- frame/fast-unstake/src/mock.rs | 2 +- frame/fast-unstake/src/tests.rs | 59 +++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 734be35158275..effcf1d808b34 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -30,7 +30,7 @@ use sp_runtime::{ }; use frame_system::RawOrigin; -use pallet_nomination_pools::{LastPoolId, PoolId, *}; +use pallet_nomination_pools::{BondedPools, LastPoolId, PoolId, PoolState}; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; pub type AccountId = u128; diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 2c0018aa8788c..bf5f56cec5691 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -28,7 +28,9 @@ use frame_support::{ weights::{extract_actual_weight, GetDispatchInfo}, }; use pallet_balances::Error as BalancesError; -use pallet_nomination_pools::{BondedPool, PoolId, *}; +use pallet_nomination_pools::{ + BondedPool, BondedPools, LastPoolId, PoolId, PoolRoles, RewardPools, +}; use pallet_staking::RewardDestination; use sp_runtime::{ @@ -58,40 +60,55 @@ fn test_setup_works() { } #[test] -fn cannot_register_if_in_queue() { - new_test_ext().execute_with(|| {}); +fn cannot_register_if_not_bonded() { + new_test_ext().execute_with(|| { + let stash = 1; + let ctrl = 2; + // Mint accounts 1 and 2 with 200 tokens. + for i in [stash, ctrl] { + let _ = Balances::make_free_balance_be(&1, 200); + } + // Attempt to fast unstake. + assert_noop!( + FastUnstake::register_fast_unstake(Origin::signed(1), Some(1_u32)), + Error::::NotController + ); + }); } #[test] -fn cannot_register_if_head() { - new_test_ext().execute_with(|| {}); +fn register_works() { + new_test_ext().execute_with(|| { + let stash = 1; + let ctrl = 2; + // Mint accounts 1 and 2 with 200 tokens. + for i in [stash, ctrl] { + let _ = Balances::make_free_balance_be(&1, 200); + } + // Account 1 bonds 200 tokens (stash) with account 2 as the controller. + assert_ok!(Staking::bond(Origin::signed(1), 2, 100, RewardDestination::Controller)); + + // Stash nominates a validator + assert_ok!(Staking::nominate(Origin::signed(ctrl), vec![3_u128])); + + // Controller account registers for fast unstake. + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32))); + }); } #[test] -fn cannot_register_if_has_unlocking_chunks() { +fn cannot_register_if_in_queue() { new_test_ext().execute_with(|| {}); } #[test] -fn cannot_register_if_not_bonded() { +fn cannot_register_if_head() { new_test_ext().execute_with(|| {}); } #[test] -fn register_works() { - new_test_ext().execute_with(|| { - // mint accounts 1-2 with 200 units of token - for i in 1..2 { - let _ = Balances::make_free_balance_be(&i, 200); - } - run_to_block(2); - // account 1 bond (stash) - // account 2: controller - // bond 100 tokens - // reward destination to controller account - assert_ok!(Staking::bond(Origin::signed(1), 2, 100, RewardDestination::Controller)); - // TODO: register for unstake - }); +fn cannot_register_if_has_unlocking_chunks() { + new_test_ext().execute_with(|| {}); } #[test] From fd6e4ad0e1e3b0d25277756a79d08d866138326a Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 23 Aug 2022 17:44:51 +0100 Subject: [PATCH 25/81] note --- frame/fast-unstake/src/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index bf5f56cec5691..b8087c2d6b881 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -89,6 +89,7 @@ fn register_works() { assert_ok!(Staking::bond(Origin::signed(1), 2, 100, RewardDestination::Controller)); // Stash nominates a validator + // NOTE: not sure where this validator is coming from (not an actual validator). assert_ok!(Staking::nominate(Origin::signed(ctrl), vec![3_u128])); // Controller account registers for fast unstake. From ac10d8ebc9801ea4c8f9cc7a7a93e4934c0b6369 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Wed, 24 Aug 2022 10:25:38 +0100 Subject: [PATCH 26/81] make UnstakeRequest fields pub --- frame/fast-unstake/src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 3cc9e1ba7d8ec..937d9a3a42848 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -141,11 +141,11 @@ pub mod pallet { #[derive(Encode, Decode, Eq, PartialEq, Clone, scale_info::TypeInfo)] pub struct UnstakeRequest { /// Their stash account. - stash: AccountId, + pub stash: AccountId, /// The list of eras for which they have been checked. - checked: Vec, + pub checked: Vec, /// The pool they wish to join, if any. - maybe_pool_id: Option, + pub maybe_pool_id: Option, } /// The current "head of the queue" being unstaked. @@ -279,7 +279,6 @@ pub mod pallet { Head::::get().map_or(true, |UnstakeRequest { stash, .. }| stash != stash), Error::::AlreadyHead ); - Queue::::remove(stash); Ok(()) } @@ -291,7 +290,6 @@ pub mod pallet { pub fn control(origin: OriginFor, eras_to_check: EraIndex) -> DispatchResult { let _ = T::ControlOrigin::ensure_origin(origin)?; ErasToCheckPerBlock::::put(eras_to_check); - Ok(()) } } From 96fc5f52e7745dea632f1722322c435b119c0b8b Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Wed, 24 Aug 2022 10:43:06 +0100 Subject: [PATCH 27/81] some tests --- frame/fast-unstake/src/tests.rs | 162 +++++++++++++++++++++++++++----- 1 file changed, 136 insertions(+), 26 deletions(-) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index b8087c2d6b881..3977eb531dc36 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -59,13 +59,43 @@ fn test_setup_works() { }) } +/// Utility function to mint a stash and controller account with 200 tokens +/// and start staking: stash bonds 100 tokens and nominates account 3. +/// returns the accounts [stash, ctrl] +fn mint_and_initiate_staking() -> (AccountId, AccountId) { + let stash = 1; + let ctrl = 2; + // Mint accounts 1 and 2 with 200 tokens. + for i in [stash, ctrl] { + let _ = Balances::make_free_balance_be(&1, 200); + } + // Account 1 bonds 200 tokens (stash) with account 2 as the controller. + assert_ok!(Staking::bond(Origin::signed(1), 2, 100, RewardDestination::Controller)); + + // Stash nominates a validator + // NOTE: not sure where this validator is coming from (not an actual validator). + assert_ok!(Staking::nominate(Origin::signed(ctrl), vec![3_u128])); + + return (stash, ctrl) +} + +#[test] +fn register_works() { + new_test_ext().execute_with(|| { + // Initiate staking position + let (stash, ctrl) = mint_and_initiate_staking(); + // Controller account registers for fast unstake. + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32))); + // Ensure stash is in the queue. + assert_ne!(Queue::::get(stash), None); + }); +} + #[test] fn cannot_register_if_not_bonded() { new_test_ext().execute_with(|| { - let stash = 1; - let ctrl = 2; // Mint accounts 1 and 2 with 200 tokens. - for i in [stash, ctrl] { + for i in 1..2 { let _ = Balances::make_free_balance_be(&1, 200); } // Attempt to fast unstake. @@ -77,54 +107,134 @@ fn cannot_register_if_not_bonded() { } #[test] -fn register_works() { +fn cannot_register_if_in_queue() { new_test_ext().execute_with(|| { - let stash = 1; - let ctrl = 2; - // Mint accounts 1 and 2 with 200 tokens. - for i in [stash, ctrl] { - let _ = Balances::make_free_balance_be(&1, 200); - } - // Account 1 bonds 200 tokens (stash) with account 2 as the controller. - assert_ok!(Staking::bond(Origin::signed(1), 2, 100, RewardDestination::Controller)); + // Initiate staking position + let (stash, ctrl) = mint_and_initiate_staking(); + // Insert some Queue item + Queue::::insert(stash, Some(1_u32)); + // Cannot re-register, already in queue + assert_noop!( + FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)), + Error::::AlreadyQueued + ); + }); +} - // Stash nominates a validator - // NOTE: not sure where this validator is coming from (not an actual validator). - assert_ok!(Staking::nominate(Origin::signed(ctrl), vec![3_u128])); +#[test] +fn cannot_register_if_head() { + new_test_ext().execute_with(|| { + // Initiate staking position + let (stash, ctrl) = mint_and_initiate_staking(); + // Insert some Head item for stash + Head::::put(UnstakeRequest { + stash: stash.clone(), + checked: vec![], + maybe_pool_id: None, + }); + // Controller attempts to regsiter + assert_noop!( + FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)), + Error::::AlreadyHead + ); + }); +} - // Controller account registers for fast unstake. - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32))); +#[test] +fn cannot_register_if_has_unlocking_chunks() { + new_test_ext().execute_with(|| { + // Initiate staking position + let (stash, ctrl) = mint_and_initiate_staking(); + // Start unbonding half of staked tokens + assert_ok!(Staking::unbond(Origin::signed(ctrl), 50_u128)); + // Cannot regsiter for fast unstake with unlock chunks active + assert_noop!( + FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)), + Error::::NotFullyBonded + ); }); } #[test] -fn cannot_register_if_in_queue() { - new_test_ext().execute_with(|| {}); +fn deregister_works() { + new_test_ext().execute_with(|| { + // Initiate staking position + let (stash, ctrl) = mint_and_initiate_staking(); + // Controller account registers for fast unstake. + FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)); + // Controller then changes mind and deregisters. + assert_ok!(FastUnstake::deregister(Origin::signed(ctrl))); + // Ensure stash no longer exists in the queue. + assert_eq!(Queue::::get(stash), None); + }); } #[test] -fn cannot_register_if_head() { - new_test_ext().execute_with(|| {}); +fn cannot_deregister_if_not_controller() { + new_test_ext().execute_with(|| { + // Initiate staking position + let (stash, ctrl) = mint_and_initiate_staking(); + // Controller account registers for fast unstake. + FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)); + // Stash tries to deregister. + assert_noop!( + FastUnstake::deregister(Origin::signed(stash)), + Error::::NotController + ); + }); } #[test] -fn cannot_register_if_has_unlocking_chunks() { - new_test_ext().execute_with(|| {}); +fn cannot_deregister_if_not_queued() { + new_test_ext().execute_with(|| { + // Initiate staking position + let (stash, ctrl) = mint_and_initiate_staking(); + // Controller tries to deregister without first registering + assert_noop!(FastUnstake::deregister(Origin::signed(ctrl)), Error::::NotQueued); + }); } #[test] -fn deregister_works() { - new_test_ext().execute_with(|| {}); +fn cannot_deregister_already_head() { + new_test_ext().execute_with(|| { + // Initiate staking position + let (stash, ctrl) = mint_and_initiate_staking(); + // Controller attempts to regsiter, should fail + FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)); + // Insert some Head item for stash. + Head::::put(UnstakeRequest { + stash: stash.clone(), + checked: vec![], + maybe_pool_id: None, + }); + // Controller attempts to deregister + assert_noop!(FastUnstake::deregister(Origin::root()), Error::::AlreadyHead); + }); } #[test] fn control_works() { - new_test_ext().execute_with(|| {}); + new_test_ext().execute_with(|| { + // account with control (root) origin wants to only check 1 era per block. + assert_ok!(FastUnstake::control(Origin::root(), 1_u32)); + }); +} + +#[test] +fn control_must_be_control_origin() { + new_test_ext().execute_with(|| { + // account without control (root) origin wants to only check 1 era per block. + assert_noop!(FastUnstake::control(Origin::signed(1), 1_u32), BadOrigin); + }); } #[test] fn unstake_paused_mid_election() { new_test_ext().execute_with(|| { + // Initiate staking position + let (stash, ctrl) = mint_and_initiate_staking(); + // Controller account registers for fast unstake. + FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)); todo!( "a dude is being unstaked, midway being checked, election happens, they are still not exposed, but a new era needs to be checked, therefore this unstake takes longer than From 12fff9d1d857c86d524451dc8c6d9a5f672028b5 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Wed, 24 Aug 2022 10:44:07 +0100 Subject: [PATCH 28/81] fix origin --- frame/fast-unstake/src/tests.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 3977eb531dc36..5350bbb25d0ee 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -177,10 +177,7 @@ fn cannot_deregister_if_not_controller() { // Controller account registers for fast unstake. FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)); // Stash tries to deregister. - assert_noop!( - FastUnstake::deregister(Origin::signed(stash)), - Error::::NotController - ); + assert_noop!(FastUnstake::deregister(Origin::signed(stash)), Error::::NotController); }); } @@ -208,7 +205,7 @@ fn cannot_deregister_already_head() { maybe_pool_id: None, }); // Controller attempts to deregister - assert_noop!(FastUnstake::deregister(Origin::root()), Error::::AlreadyHead); + assert_noop!(FastUnstake::deregister(Origin::signed(ctrl)), Error::::AlreadyHead); }); } From f46ed2f5e26feeece93e2fbc12ba90d80267bb3f Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Wed, 24 Aug 2022 10:44:18 +0100 Subject: [PATCH 29/81] fmt --- frame/fast-unstake/src/tests.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 5350bbb25d0ee..18ff9cb35e283 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -177,7 +177,10 @@ fn cannot_deregister_if_not_controller() { // Controller account registers for fast unstake. FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)); // Stash tries to deregister. - assert_noop!(FastUnstake::deregister(Origin::signed(stash)), Error::::NotController); + assert_noop!( + FastUnstake::deregister(Origin::signed(stash)), + Error::::NotController + ); }); } From 40ec631d94ba18af44f217d8c5d2e17caf3e00f4 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Fri, 26 Aug 2022 19:06:40 +0100 Subject: [PATCH 30/81] add fast_unstake_events_since_last_call --- frame/fast-unstake/src/mock.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index effcf1d808b34..af978184a3ae0 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -413,3 +413,18 @@ pub(crate) fn run_to_block(n: u64) { FastUnstake::on_initialize(System::block_number()); } } + +parameter_types! { + storage FastUnstakeEvents: u32 = 0; +} + +pub(crate) fn fast_unstake_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::FastUnstake(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = FastUnstakeEvents::get(); + FastUnstakeEvents::set(&(events.len() as u32)); + events.into_iter().skip(already_seen as usize).collect() +} From 1ba119464bf334349388f84c6c262db2ebbd5f53 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Fri, 26 Aug 2022 22:14:57 +0100 Subject: [PATCH 31/81] text --- frame/fast-unstake/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 937d9a3a42848..6384f27382dee 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -263,7 +263,7 @@ pub mod pallet { /// Deregister oneself from the fast-unstake (and possibly joining a pool). /// - /// This is useful id one is registered, they are still waiting, and they change their mind. + /// This is useful if one is registered, they are still waiting, and they change their mind. /// /// Note that the associated stash is still fully unbonded and chilled as a consequence of /// calling `register_fast_unstake`. This should probably be followed by a call to @@ -490,8 +490,7 @@ pub mod pallet { fn pre_dispatch( self, // NOTE: we want to prevent this stash-controller pair from doing anything in the - // staking system as long as they are registered here. `who` can be a stash or a - // controller. + // staking system as long as they are registered here. stash_or_controller: &Self::AccountId, call: &Self::Call, _info: &sp_runtime::traits::DispatchInfoOf, From ca45c78a7a0a3a486ba09d74e34391d5434278ae Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sun, 28 Aug 2022 15:15:31 +0430 Subject: [PATCH 32/81] rewrite some benchmes and fix them -- the outcome is still strange --- frame/fast-unstake/src/benchmarking.rs | 96 +++++++++++++++----------- frame/fast-unstake/src/lib.rs | 92 ++++++++++++------------ 2 files changed, 104 insertions(+), 84 deletions(-) diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index e9bf8e5e90568..e74b9ee031a66 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -21,13 +21,16 @@ use crate::{Pallet as FastUnstake, *}; use frame_benchmarking::{benchmarks, whitelist_account}; -use frame_support::traits::{Currency, EnsureOrigin, Get}; +use frame_support::{ + assert_ok, + traits::{Currency, EnsureOrigin, Get, Hooks}, +}; use frame_system::RawOrigin; use pallet_nomination_pools::{Pallet as Pools, PoolId}; use pallet_staking::Pallet as Staking; -use sp_runtime::traits::{Bounded, StaticLookup, Zero}; +use sp_runtime::traits::{StaticLookup, Zero}; +use sp_staking::EraIndex; use sp_std::prelude::*; -use frame_support::traits::Hooks; const USER_SEED: u32 = 0; const DEFAULT_BACKER_PER_VALIDATOR: u32 = 128; @@ -79,33 +82,18 @@ fn setup_pool() -> PoolId { pallet_nomination_pools::LastPoolId::::get() } -fn setup_staking(v: u32) { +fn setup_staking(v: u32, until: EraIndex) { let ed = CurrencyOf::::minimum_balance(); - // make sure there are enough validator candidates - // NOTE: seems like these actually don't need to be real validators.. - for seed in 0..(pallet_staking::Validators::::iter().count().saturating_sub(v as usize)) { - let account = - frame_benchmarking::account::("validator", seed as u32, USER_SEED); - let account_lookup = l::(account.clone()); - - let stake = ed * 100u32.into(); - CurrencyOf::::make_free_balance_be(&account, stake * 10u32.into()); - - Staking::::bond( - RawOrigin::Signed(account.clone()).into(), - account_lookup.clone(), - stake, - pallet_staking::RewardDestination::Controller, - ) - .unwrap(); - Staking::::validate(RawOrigin::Signed(account.clone()).into(), Default::default()) - .unwrap(); - } + log!(debug, "registering {} validators and {} eras.", v, until); - let validators = pallet_staking::Validators::::iter_keys().collect::>(); + // our validators don't actually need to registered in staking -- just generate `v` random + // accounts. + let validators = (0..v) + .map(|x| frame_benchmarking::account::("validator", x, USER_SEED)) + .collect::>(); - for era in 0..(2 * ::BondingDuration::get()) { + for era in 0..=until { let others = (0..DEFAULT_BACKER_PER_VALIDATOR) .map(|s| { let who = frame_benchmarking::account::("nominator", era, s); @@ -131,32 +119,58 @@ benchmarks! { on_idle_unstake { let who = create_unexposed_nominator::(); let pool_id = setup_pool::(); - FastUnstake::::register_fast_unstake( + assert_ok!(FastUnstake::::register_fast_unstake( RawOrigin::Signed(who.clone()).into(), Some(pool_id) - ).unwrap(); + )); ErasToCheckPerBlock::::put(1); - // no era to check, because staking era is still 0. - assert_eq!(pallet_staking::CurrentEra::::get().unwrap_or_default(), 0); - - // run on_idle once. + // run on_idle once. This will check era 0. + assert_eq!(Head::::get(), None); on_idle_full_block::(); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: who.clone(), checked: vec![0], maybe_pool_id: Some(1) }) + ); } : { on_idle_full_block::(); } - verify {} + verify { + // TODO: make sure who os not staked anymore, and has indeed joined a pool. + } // on_idle, when we check some number of eras, on_idle_check { - // staking should have enough validators and progress to a state where enough eras exist. - // add non-exposed nominator - // register - // + // number of validators + let v in 1 .. 1000; + // number of eras to check. + let u in 1 .. (::BondingDuration::get() / 4); + + ErasToCheckPerBlock::::put(u); + pallet_staking::CurrentEra::::put(u); + + // setup staking with v validators and u eras of data (0..=u) + setup_staking::(v, u); + let who = create_unexposed_nominator::(); + assert_ok!(FastUnstake::::register_fast_unstake( + RawOrigin::Signed(who.clone()).into(), + None, + )); + + // no one is queued thus far. + assert_eq!(Head::::get(), None); + } + : { + on_idle_full_block::(); + } + verify { + let checked = (1..=u).rev().collect::>(); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: who.clone(), checked, maybe_pool_id: None }) + ); } - : {} - verify {} // same as above, but we do the entire check and realize that we had to slash our nominator now. on_idle_check_slash {} @@ -172,10 +186,10 @@ benchmarks! { deregister { let who = create_unexposed_nominator::(); - FastUnstake::::register_fast_unstake( + assert_ok!(FastUnstake::::register_fast_unstake( RawOrigin::Signed(who.clone()).into(), None - ).unwrap(); + )); whitelist_account!(who); } :_(RawOrigin::Signed(who.clone())) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 67716e1c5d7c0..e4594c859e5cb 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -61,23 +61,21 @@ macro_rules! log { #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_election_provider_support::ElectionProvider; + use frame_support::{ + pallet_prelude::*, + traits::{Currency, IsSubType}, + }; use frame_system::{pallet_prelude::*, RawOrigin}; - use sp_std::prelude::*; - - use frame_support::traits::{Currency, IsSubType}; use pallet_nomination_pools::PoolId; + use pallet_staking::Pallet as Staking; use sp_runtime::{ traits::{Saturating, Zero}, transaction_validity::{InvalidTransaction, TransactionValidityError}, DispatchResult, }; use sp_staking::EraIndex; - - use frame_election_provider_support::ElectionProvider; - use pallet_staking::Pallet as Staking; - - use sp_std::{ops::Div, vec::Vec}; + use sp_std::{prelude::*, vec::Vec}; pub trait WeightInfo { fn register_fast_unstake() -> Weight; @@ -85,7 +83,7 @@ pub mod pallet { fn control() -> Weight; fn on_idle_unstake() -> Weight; - fn on_idle_check(e: u32) -> Weight; + fn on_idle_check(v: u32, e: u32) -> Weight; } impl WeightInfo for () { @@ -101,7 +99,7 @@ pub mod pallet { fn on_idle_unstake() -> Weight { 0 } - fn on_idle_check(_: u32) -> Weight { + fn on_idle_check(_: u32, _: u32) -> Weight { 0 } } @@ -136,14 +134,16 @@ pub mod pallet { } /// An unstake request. - #[derive(Encode, Decode, Eq, PartialEq, Clone, scale_info::TypeInfo)] + #[derive( + Encode, Decode, Eq, PartialEq, Clone, scale_info::TypeInfo, frame_support::RuntimeDebug, + )] pub struct UnstakeRequest { /// Their stash account. - stash: AccountId, + pub(crate) stash: AccountId, /// The list of eras for which they have been checked. - checked: Vec, + pub(crate) checked: Vec, /// The pool they wish to join, if any. - maybe_pool_id: Option, + pub(crate) maybe_pool_id: Option, } /// The current "head of the queue" being unstaked. @@ -281,30 +281,45 @@ pub mod pallet { /// 1. We assume this is only ever called once per `on_idle`. This is because we know that /// in all use cases, even a single nominator cannot be unbonded in a single call. Multiple /// calls to this function are thus not needed. 2. We will only mark a staker + #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))] fn process_head(remaining_weight: Weight) -> Weight { let eras_to_check_per_block = ErasToCheckPerBlock::::get(); if eras_to_check_per_block.is_zero() { return T::DbWeight::get().reads(1) } + // NOTE: here we're assuming that the number of validators has only ever increased, + // meaning that the number of exposures to check is either this per era, or less.s + let validator_count = pallet_staking::ValidatorCount::::get(); + + // determine the number of eras to check. This is based on both `ErasToCheckPerBlock` + // and `remaining_weight` passed on to us from the runtime executive. #[cfg(feature = "runtime-benchmarks")] - let total_worse_case_weight = ::WeightInfo::on_idle_check(eras_to_check_per_block) - .max(::WeightInfo::on_idle_unstake()); - // TODO: not sure if this is needed. + let final_eras_to_check = eras_to_check_per_block; #[cfg(not(feature = "runtime-benchmarks"))] - let total_worse_case_weight = 0; + let final_eras_to_check = { + let worse_weight = |v, u| { + ::WeightInfo::on_idle_check(v, u) + .max(::WeightInfo::on_idle_unstake()) + }; + let mut try_eras_to_check = eras_to_check_per_block; + while worse_weight(validator_count, try_eras_to_check) > remaining_weight { + try_eras_to_check.saturating_dec(); + if try_eras_to_check.is_zero() { + return T::DbWeight::get().reads(1) + } + } - if total_worse_case_weight > remaining_weight { - log!(warn, "total_worse_case_weight > remaining_weight, early exiting"); - return T::DbWeight::get().reads(1) - } + drop(eras_to_check_per_block); + try_eras_to_check + }; if ::ElectionProvider::ongoing() { // NOTE: we assume `ongoing` does not consume any weight. // there is an ongoing election -- we better not do anything. Imagine someone is not // exposed anywhere in the last era, and the snapshot for the election is already // taken. In this time period, we don't want to accidentally unstake them. - return T::DbWeight::get().reads(1) + return T::DbWeight::get().reads(2) } let UnstakeRequest { stash, mut checked, maybe_pool_id } = match Head::::take() @@ -320,27 +335,12 @@ pub mod pallet { }) { None => { // There's no `Head` and nothing in the `Queue`, nothing to do here. - return T::DbWeight::get().reads_writes(2, 1) + return T::DbWeight::get().reads(4) }, Some(head) => head, }; - // determine the amount of eras to check. this is minimum of two criteria: - // `ErasToCheckPerBlock`, and how much weight is given to the on_idle hook. For the sake - // of simplicity, we assume we check at most one staker's eras per-block. - let final_eras_to_check = { - // NOTE: here we're assuming that the number of validators has only ever increased, - // meaning that the number of exposures to check is either this per era, or less. - let weight_per_era_check = T::DbWeight::get().reads(1) * - pallet_staking::ValidatorCount::::get() as Weight; - let eras_to_check_weight_limit = remaining_weight.div(weight_per_era_check); - eras_to_check_per_block.min(eras_to_check_weight_limit as u32) - }; - - // return weight consumed if no eras to check.. - if final_eras_to_check.is_zero() { - return T::DbWeight::get().reads_writes(2, 1) - } + log!(debug, "checking {:?}", stash); // the range that we're allowed to check in this round. let current_era = pallet_staking::CurrentEra::::get().unwrap_or_default(); @@ -353,7 +353,11 @@ pub mod pallet { current_era) .rev() .collect::>(); - debug_assert!(total_check_range.len() <= bonding_duration as usize); + debug_assert!( + total_check_range.len() <= (bonding_duration + 1) as usize, + "{:?}", + total_check_range + ); // remove eras that have already been checked, take a maximum of // final_eras_to_check. @@ -364,6 +368,8 @@ pub mod pallet { .collect::>() }; + log!(debug, "{} eras to check: {:?}", eras_to_check.len(), eras_to_check); + if eras_to_check.is_empty() { // `stash` is not exposed in any era now -- we can let go of them now. let num_slashing_spans = Staking::::slashing_spans(&stash).iter().count() as u32; @@ -432,7 +438,7 @@ pub mod pallet { Self::deposit_event(Event::::Checked { stash, eras: eras_to_check }); } - ::WeightInfo::on_idle_check(final_eras_to_check) + ::WeightInfo::on_idle_check(validator_count, final_eras_to_check) } } From 956ac7c1974a70f01a24ccf08d17145dfcb7d69b Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sun, 28 Aug 2022 16:44:49 +0430 Subject: [PATCH 33/81] Fix weights --- frame/fast-unstake/src/benchmarking.rs | 53 +++++++++++++++++--------- frame/fast-unstake/src/lib.rs | 14 ++++++- frame/fast-unstake/src/tests.rs | 4 +- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index e74b9ee031a66..26f56e1026061 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Benchmarking for pallet-example-basic. +//! Benchmarking for pallet-fast-unstake. #![cfg(feature = "runtime-benchmarks")] @@ -34,6 +34,7 @@ use sp_std::prelude::*; const USER_SEED: u32 = 0; const DEFAULT_BACKER_PER_VALIDATOR: u32 = 128; +const MAX_VALIDATORS: u32 = 128; type CurrencyOf = ::Currency; @@ -45,22 +46,35 @@ fn l( fn create_unexposed_nominator() -> T::AccountId { let account = frame_benchmarking::account::("nominator_42", 0, USER_SEED); + fund_and_bond_account::(&account); + account +} + +fn fund_and_bond_account(account: &T::AccountId) { let stake = CurrencyOf::::minimum_balance() * 100u32.into(); CurrencyOf::::make_free_balance_be(&account, stake * 10u32.into()); let account_lookup = l::(account.clone()); // bond and nominate ourselves, this will guarantee that we are not backing anyone. - Staking::::bond( + assert_ok!(Staking::::bond( RawOrigin::Signed(account.clone()).into(), account_lookup.clone(), stake, pallet_staking::RewardDestination::Controller, - ) - .unwrap(); - Staking::::nominate(RawOrigin::Signed(account.clone()).into(), vec![account_lookup]) - .unwrap(); + )); + assert_ok!( + Staking::::nominate(RawOrigin::Signed(account.clone()).into(), vec![account_lookup]) + ); +} - account +/// All events of the `Balances` pallet. +// TODO: reused this in the tests mock. +pub(crate) fn fast_unstake_events() -> Vec> { + frame_system::Pallet::::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| ::Event::from(e).try_into().ok()) + .collect::>() } fn setup_pool() -> PoolId { @@ -137,15 +151,19 @@ benchmarks! { on_idle_full_block::(); } verify { - // TODO: make sure who os not staked anymore, and has indeed joined a pool. + assert!(matches!( + fast_unstake_events::().last(), + Some(Event::Unstaked { .. }) + )); } // on_idle, when we check some number of eras, on_idle_check { - // number of validators - let v in 1 .. 1000; - // number of eras to check. - let u in 1 .. (::BondingDuration::get() / 4); + // number of eras multiplied by validators in that era. + let x in (::BondingDuration::get() * 1) .. (::BondingDuration::get() * MAX_VALIDATORS); + + let v = x / ::BondingDuration::get(); + let u = ::BondingDuration::get(); ErasToCheckPerBlock::::put(u); pallet_staking::CurrentEra::::put(u); @@ -170,13 +188,12 @@ benchmarks! { Head::::get(), Some(UnstakeRequest { stash: who.clone(), checked, maybe_pool_id: None }) ); + assert!(matches!( + fast_unstake_events::().last(), + Some(Event::Checked { .. }) + )); } - // same as above, but we do the entire check and realize that we had to slash our nominator now. - on_idle_check_slash {} - : {} - verify {} - register_fast_unstake { let who = create_unexposed_nominator::(); whitelist_account!(who); diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index e4594c859e5cb..29e45eb2d76f8 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -119,7 +119,9 @@ pub mod pallet { > + pallet_nomination_pools::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type Event: From> + + IsType<::Event> + + TryInto>; /// The amount of balance slashed per each era that was wastefully checked. /// @@ -418,6 +420,14 @@ pub mod pallet { Self::is_exposed_in_era(&stash, e) }); + log!( + debug, + "checked {:?} eras, (v: {:?}, u: {:?})", + eras_checked, + validator_count, + eras_to_check.len() + ); + // NOTE: you can be extremely unlucky and get slashed here: You are not exposed in // the last 28 eras, have registered yourself to be unstaked, midway being checked, // you are exposed. diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 1bf8df5cd9e9b..5e8af17dbee53 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Tests for pallet-example-basic. +//! Tests for pallet-fast-unstake. use crate::*; use frame_support::{ From db7cfecba8ecb4a83b60b9447538a32f32069358 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sun, 28 Aug 2022 16:54:47 +0430 Subject: [PATCH 34/81] cleanup --- frame/election-provider-support/src/traits.rs | 4 - frame/fast-unstake/README.md | 240 ------------------ frame/fast-unstake/src/benchmarking.rs | 7 +- frame/fast-unstake/src/lib.rs | 14 +- 4 files changed, 13 insertions(+), 252 deletions(-) delete mode 100644 frame/fast-unstake/README.md diff --git a/frame/election-provider-support/src/traits.rs b/frame/election-provider-support/src/traits.rs index 46e0d521b3604..ed812e2e0f2c4 100644 --- a/frame/election-provider-support/src/traits.rs +++ b/frame/election-provider-support/src/traits.rs @@ -122,8 +122,4 @@ where voter_at: impl Fn(Self::VoterIndex) -> Option, target_at: impl Fn(Self::TargetIndex) -> Option, ) -> Result>, Error>; - - /// Build a fake version of self with the given number of voters and targets. - #[cfg(feature = "srd")] - fn fake_from(v: u32, t: u32) -> Self; } diff --git a/frame/fast-unstake/README.md b/frame/fast-unstake/README.md deleted file mode 100644 index 358829192f11d..0000000000000 --- a/frame/fast-unstake/README.md +++ /dev/null @@ -1,240 +0,0 @@ - -# Basic Example Pallet - - -The Example: A simple example of a FRAME pallet demonstrating -concepts, APIs and structures common to most FRAME runtimes. - -Run `cargo doc --package pallet-example-basic --open` to view this pallet's documentation. - -**This pallet serves as an example and is not meant to be used in production.** - -### Documentation Guidelines: - - - - - -### Documentation Template:
- -Copy and paste this template from frame/examples/basic/src/lib.rs into file -`frame//src/lib.rs` of your own custom pallet and complete it. -

-// Add heading with custom pallet name
-
-\#  Pallet
-
-// Add simple description
-
-// Include the following links that shows what trait needs to be implemented to use the pallet
-// and the supported dispatchables that are documented in the Call enum.
-
-- \[`::Config`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/trait.Config.html)
-- \[`Call`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/enum.Call.html)
-- \[`Module`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/struct.Module.html)
-
-\## Overview
-
-
-// Short description of pallet's purpose.
-// Links to Traits that should be implemented.
-// What this pallet is for.
-// What functionality the pallet provides.
-// When to use the pallet (use case examples).
-// How it is used.
-// Inputs it uses and the source of each input.
-// Outputs it produces.
-
-
-
-
-\## Terminology
-
-// Add terminology used in the custom pallet. Include concepts, storage items, or actions that you think
-// deserve to be noted to give context to the rest of the documentation or pallet usage. The author needs to
-// use some judgment about what is included. We don't want a list of every storage item nor types - the user
-// can go to the code for that. For example, "transfer fee" is obvious and should not be included, but
-// "free balance" and "reserved balance" should be noted to give context to the pallet.
-// Please do not link to outside resources. The reference docs should be the ultimate source of truth.
-
-
-
-\## Goals
-
-// Add goals that the custom pallet is designed to achieve.
-
-
-
-\### Scenarios
-
-
-
-\#### 
-
-// Describe requirements prior to interacting with the custom pallet.
-// Describe the process of interacting with the custom pallet for this scenario and public API functions used.
-
-\## Interface
-
-\### Supported Origins
-
-// What origins are used and supported in this pallet (root, signed, none)
-// i.e. root when \`ensure_root\` used
-// i.e. none when \`ensure_none\` used
-// i.e. signed when \`ensure_signed\` used
-
-\`inherent\` 
-
-
-
-
-\### Types
-
-// Type aliases. Include any associated types and where the user would typically define them.
-
-\`ExampleType\` 
-
-
-
-// Reference documentation of aspects such as `storageItems` and `dispatchable` functions should only be
-// included in the https://docs.rs Rustdocs for Substrate and not repeated in the README file.
-
-\### Dispatchable Functions
-
-
-
-// A brief description of dispatchable functions and a link to the rustdoc with their actual documentation.
-
-// MUST have link to Call enum
-// MUST have origin information included in function doc
-// CAN have more info up to the user
-
-\### Public Functions
-
-
-
-// A link to the rustdoc and any notes about usage in the pallet, not for specific functions.
-// For example, in the Balances Pallet: "Note that when using the publicly exposed functions,
-// you (the runtime developer) are responsible for implementing any necessary checks
-// (e.g. that the sender is the signer) before calling a function that will affect storage."
-
-
-
-// It is up to the writer of the respective pallet (with respect to how much information to provide).
-
-\#### Public Inspection functions - Immutable (getters)
-
-// Insert a subheading for each getter function signature
-
-\##### \`example_getter_name()\`
-
-// What it returns
-// Why, when, and how often to call it
-// When it could panic or error
-// When safety issues to consider
-
-\#### Public Mutable functions (changing state)
-
-// Insert a subheading for each setter function signature
-
-\##### \`example_setter_name(origin, parameter_name: T::ExampleType)\`
-
-// What state it changes
-// Why, when, and how often to call it
-// When it could panic or error
-// When safety issues to consider
-// What parameter values are valid and why
-
-\### Storage Items
-
-// Explain any storage items included in this pallet
-
-\### Digest Items
-
-// Explain any digest items included in this pallet
-
-\### Inherent Data
-
-// Explain what inherent data (if any) is defined in the pallet and any other related types
-
-\### Events:
-
-// Insert events for this pallet if any
-
-\### Errors:
-
-// Explain what generates errors
-
-\## Usage
-
-// Insert 2-3 examples of usage and code snippets that show how to
-// use  Pallet in a custom pallet.
-
-\### Prerequisites
-
-// Show how to include necessary imports for  and derive
-// your pallet configuration trait with the `INSERT_CUSTOM_PALLET_NAME` trait.
-
-\```rust
-use ;
-
-pub trait Config: ::Config { }
-\```
-
-\### Simple Code Snippet
-
-// Show a simple example (e.g. how to query a public getter function of )
-
-\### Example from FRAME
-
-// Show a usage example in an actual runtime
-
-// See:
-// - Substrate TCR https://github.com/parity-samples/substrate-tcr
-// - Substrate Kitties https://shawntabrizi.github.io/substrate-collectables-workshop/#/
-
-\## Genesis Config
-
-
-
-\## Dependencies
-
-// Dependencies on other FRAME pallets and the genesis config should be mentioned,
-// but not the Rust Standard Library.
-// Genesis configuration modifications that may be made to incorporate this pallet
-// Interaction with other pallets
-
-
-
-\## Related Pallets
-
-// Interaction with other pallets in the form of a bullet point list
-
-\## References
-
-
-
-// Links to reference material, if applicable. For example, Phragmen, W3F research, etc.
-// that the implementation is based on.
-

- -License: Unlicense diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index 26f56e1026061..93c7d499c4440 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -62,9 +62,10 @@ fn fund_and_bond_account(account: &T::AccountId) { stake, pallet_staking::RewardDestination::Controller, )); - assert_ok!( - Staking::::nominate(RawOrigin::Signed(account.clone()).into(), vec![account_lookup]) - ); + assert_ok!(Staking::::nominate( + RawOrigin::Signed(account.clone()).into(), + vec![account_lookup] + )); } /// All events of the `Balances` pallet. diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 29e45eb2d76f8..9b845033f843f 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -18,11 +18,12 @@ //! A pallet that's designed to ONLY do: //! //! If a nominator is not exposed at all in any `ErasStakers` (i.e. "has not backed any validators -//! in the last 28 days"), then they can register themselves in this pallet, and move quickly into -//! a nomination pool. +//! in the last `BondingDuration` days"), then they can register themselves in this pallet, and move +//! quickly into a nomination pool. //! //! This pallet works of the basis of `on_idle`, meaning that it provides no guarantee about when it -//! will succeed, if at all. +//! will succeed, if at all. Moreover, the queue implementation is unordered. In case of congestion, +//! not FIFO ordering is provided. //! //! Stakers who are certain about NOT being exposed can register themselves with //! [`Call::register_fast_unstake`]. This will chill, and fully unbond the staker, and place them in @@ -31,12 +32,15 @@ //! Once queued, but not being actively processed, stakers can withdraw their request via //! [`Call::deregister`]. //! +//! Once queued, a staker wishing to unbond can perform to further action in pallet-staking. This is +//! to prevent them from accidentally exposing themselves behind a validator etc. +//! //! Once processed, if successful, no additional fees for the checking process is taken, and the //! staker is instantly unbonded. Optionally, if the have asked to join a pool, their *entire* stake //! is joined into their pool of choice. //! -//! If unsuccessful, meaning that the staker was exposed sometime in the last 28 eras they will end -//! up being slashed for the amount of wasted work they have inflicted on the chian. +//! If unsuccessful, meaning that the staker was exposed sometime in the last `BondingDuration` eras +//! they will end up being slashed for the amount of wasted work they have inflicted on the chian. #![cfg_attr(not(feature = "std"), no_std)] From db921e0f669ad13ff5d5f0860ae3d1285b3c5d5e Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Sun, 28 Aug 2022 13:55:40 +0100 Subject: [PATCH 35/81] Update frame/election-provider-support/solution-type/src/single_page.rs --- frame/election-provider-support/solution-type/src/single_page.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/election-provider-support/solution-type/src/single_page.rs b/frame/election-provider-support/solution-type/src/single_page.rs index aee60e93afbb9..a7ccf5085d2b1 100644 --- a/frame/election-provider-support/solution-type/src/single_page.rs +++ b/frame/election-provider-support/solution-type/src/single_page.rs @@ -178,7 +178,6 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { all_targets.into_iter().collect() } - } type __IndexAssignment = _feps::IndexAssignment< From f5ae1992ba109e2647cd5fc9677923d39eb19eb3 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 29 Aug 2022 10:31:52 +0430 Subject: [PATCH 36/81] fix build --- frame/election-provider-multi-phase/src/mock.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 7eff70b47eba5..915d150d039d2 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -301,6 +301,10 @@ impl ElectionProvider for MockFallback { type Error = &'static str; type DataProvider = StakingMock; + fn ongoing() -> bool { + false + } + fn elect() -> Result, Self::Error> { Self::elect_with_bounds(Bounded::max_value(), Bounded::max_value()) } From 80b5e5fb0e0907a779c4ee6cf04910aabce3b116 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 29 Aug 2022 10:54:43 +0430 Subject: [PATCH 37/81] Fix pools tests --- frame/nomination-pools/src/lib.rs | 1 + frame/nomination-pools/src/tests.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 764f0038ced02..c47fb5fa59c0a 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -2179,6 +2179,7 @@ impl Pallet { T::StakingInterface::minimum_bond() .max(MinCreateBond::::get()) .max(MinJoinBond::::get()) + .max(T::Currency::minimum_balance()) } /// Remove everything related to the given bonded pool. /// diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 5aa8e97266e0d..c815a12a7e115 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -2629,7 +2629,7 @@ mod unbond { #[test] fn partial_unbond_era_tracking() { - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().ed(1).build_and_execute(|| { // to make the depositor capable of withdrawing. StakingMinBond::set(1); MinCreateBond::::set(1); From 9fa95e9a35550b594f01edb627def7e0814a6759 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Mon, 29 Aug 2022 10:19:27 +0100 Subject: [PATCH 38/81] iterate teset + mock --- frame/fast-unstake/src/lib.rs | 2 +- frame/fast-unstake/src/mock.rs | 30 ++++++++- frame/fast-unstake/src/tests.rs | 105 +++++++++++--------------------- 3 files changed, 67 insertions(+), 70 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index b79d694b2b191..40f0f1480a75f 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -151,7 +151,7 @@ pub mod pallet { /// Their stash account. pub(crate) stash: AccountId, /// The list of eras for which they have been checked. - pub pub(crate) checked: Vec, + pub(crate) checked: Vec, /// The pool they wish to join, if any. pub(crate) maybe_pool_id: Option, } diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index af978184a3ae0..678a730fce691 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -31,6 +31,7 @@ use sp_runtime::{ use frame_system::RawOrigin; use pallet_nomination_pools::{BondedPools, LastPoolId, PoolId, PoolState}; +use pallet_staking::RewardDestination; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; pub type AccountId = u128; @@ -197,7 +198,7 @@ impl fast_unstake::WeightInfo for FastUnstakeWeightInfo { fn on_idle_unstake() -> Weight { 10 } - fn on_idle_check(e: u32) -> Weight { + fn on_idle_check(v: u32, e: u32) -> Weight { 10 } } @@ -310,6 +311,9 @@ impl Default for ExtBuilder { } } +pub(crate) const STASH: u128 = 1; +pub(crate) const CONTROLLER: u128 = 2; + impl ExtBuilder { // Add members to pool 0. pub(crate) fn add_members(mut self, members: Vec<(AccountId, Balance)>) -> Self { @@ -347,6 +351,27 @@ impl ExtBuilder { self } + /// Utility function to mint a stash and controller account with 200 tokens + /// and start staking: stash bonds 100 tokens and nominates account 3. + /// returns the accounts [stash, ctrl] + pub(crate) fn mint_and_initiate_staking() { + // Mint accounts 1 and 2 with 200 tokens. + for i in [STASH, CONTROLLER] { + let _ = Balances::make_free_balance_be(&i, 200); + } + // stash bonds 200 tokens with controller. + assert_ok!(Staking::bond( + Origin::signed(STASH), + CONTROLLER, + 100, + RewardDestination::Controller + )); + + // Stash nominates a validator + // NOTE: not sure where this validator is coming from (not an actual validator). + assert_ok!(Staking::nominate(Origin::signed(CONTROLLER), vec![3_u128])); + } + pub(crate) fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = @@ -367,6 +392,9 @@ impl ExtBuilder { // for events to be deposited. frame_system::Pallet::::set_block_number(1); + // initial staking setup with some accounts 1 and 2 + Self::mint_and_initiate_staking(); + // make a pool let amount_to_bond = MinJoinBondConfig::get(); Balances::make_free_balance_be(&10, amount_to_bond * 5); diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 5fc3b34ec32cc..67627f38bede5 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -56,44 +56,22 @@ fn test_setup_works() { assert_eq!(Staking::bonding_duration(), 3); let last_pool = LastPoolId::::get(); assert_eq!(last_pool, 1); - }) -} - -/// Utility function to mint a stash and controller account with 200 tokens -/// and start staking: stash bonds 100 tokens and nominates account 3. -/// returns the accounts [stash, ctrl] -fn mint_and_initiate_staking() -> (AccountId, AccountId) { - let stash = 1; - let ctrl = 2; - // Mint accounts 1 and 2 with 200 tokens. - for i in [stash, ctrl] { - let _ = Balances::make_free_balance_be(&1, 200); - } - // Account 1 bonds 200 tokens (stash) with account 2 as the controller. - assert_ok!(Staking::bond(Origin::signed(1), 2, 100, RewardDestination::Controller)); - - // Stash nominates a validator - // NOTE: not sure where this validator is coming from (not an actual validator). - assert_ok!(Staking::nominate(Origin::signed(ctrl), vec![3_u128])); - - return (stash, ctrl) + }); } #[test] fn register_works() { - new_test_ext().execute_with(|| { - // Initiate staking position - let (stash, ctrl) = mint_and_initiate_staking(); + ExtBuilder::default().build_and_execute(|| { // Controller account registers for fast unstake. - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32))); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); // Ensure stash is in the queue. - assert_ne!(Queue::::get(stash), None); + assert_ne!(Queue::::get(STASH), None); }); } #[test] fn cannot_register_if_not_bonded() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { // Mint accounts 1 and 2 with 200 tokens. for i in 1..2 { let _ = Balances::make_free_balance_be(&1, 200); @@ -108,14 +86,12 @@ fn cannot_register_if_not_bonded() { #[test] fn cannot_register_if_in_queue() { - new_test_ext().execute_with(|| { - // Initiate staking position - let (stash, ctrl) = mint_and_initiate_staking(); + ExtBuilder::default().build_and_execute(|| { // Insert some Queue item - Queue::::insert(stash, Some(1_u32)); + Queue::::insert(STASH, Some(1_u32)); // Cannot re-register, already in queue assert_noop!( - FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)), + FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)), Error::::AlreadyQueued ); }); @@ -123,18 +99,16 @@ fn cannot_register_if_in_queue() { #[test] fn cannot_register_if_head() { - new_test_ext().execute_with(|| { - // Initiate staking position - let (stash, ctrl) = mint_and_initiate_staking(); + ExtBuilder::default().build_and_execute(|| { // Insert some Head item for stash Head::::put(UnstakeRequest { - stash: stash.clone(), + stash: STASH.clone(), checked: vec![], maybe_pool_id: None, }); // Controller attempts to regsiter assert_noop!( - FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)), + FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)), Error::::AlreadyHead ); }); @@ -142,14 +116,12 @@ fn cannot_register_if_head() { #[test] fn cannot_register_if_has_unlocking_chunks() { - new_test_ext().execute_with(|| { - // Initiate staking position - let (stash, ctrl) = mint_and_initiate_staking(); + ExtBuilder::default().build_and_execute(|| { // Start unbonding half of staked tokens - assert_ok!(Staking::unbond(Origin::signed(ctrl), 50_u128)); + assert_ok!(Staking::unbond(Origin::signed(CONTROLLER), 50_u128)); // Cannot regsiter for fast unstake with unlock chunks active assert_noop!( - FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)), + FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)), Error::::NotFullyBonded ); }); @@ -157,28 +129,24 @@ fn cannot_register_if_has_unlocking_chunks() { #[test] fn deregister_works() { - new_test_ext().execute_with(|| { - // Initiate staking position - let (stash, ctrl) = mint_and_initiate_staking(); + ExtBuilder::default().build_and_execute(|| { // Controller account registers for fast unstake. - FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)); + FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)); // Controller then changes mind and deregisters. - assert_ok!(FastUnstake::deregister(Origin::signed(ctrl))); + assert_ok!(FastUnstake::deregister(Origin::signed(CONTROLLER))); // Ensure stash no longer exists in the queue. - assert_eq!(Queue::::get(stash), None); + assert_eq!(Queue::::get(STASH), None); }); } #[test] fn cannot_deregister_if_not_controller() { - new_test_ext().execute_with(|| { - // Initiate staking position - let (stash, ctrl) = mint_and_initiate_staking(); + ExtBuilder::default().build_and_execute(|| { // Controller account registers for fast unstake. - FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)); + FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)); // Stash tries to deregister. assert_noop!( - FastUnstake::deregister(Origin::signed(stash)), + FastUnstake::deregister(Origin::signed(STASH)), Error::::NotController ); }); @@ -186,35 +154,37 @@ fn cannot_deregister_if_not_controller() { #[test] fn cannot_deregister_if_not_queued() { - new_test_ext().execute_with(|| { - // Initiate staking position - let (stash, ctrl) = mint_and_initiate_staking(); + ExtBuilder::default().build_and_execute(|| { // Controller tries to deregister without first registering - assert_noop!(FastUnstake::deregister(Origin::signed(ctrl)), Error::::NotQueued); + assert_noop!( + FastUnstake::deregister(Origin::signed(CONTROLLER)), + Error::::NotQueued + ); }); } #[test] fn cannot_deregister_already_head() { - new_test_ext().execute_with(|| { - // Initiate staking position - let (stash, ctrl) = mint_and_initiate_staking(); + ExtBuilder::default().build_and_execute(|| { // Controller attempts to regsiter, should fail - FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)); + FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)); // Insert some Head item for stash. Head::::put(UnstakeRequest { - stash: stash.clone(), + stash: STASH.clone(), checked: vec![], maybe_pool_id: None, }); // Controller attempts to deregister - assert_noop!(FastUnstake::deregister(Origin::signed(ctrl)), Error::::AlreadyHead); + assert_noop!( + FastUnstake::deregister(Origin::signed(CONTROLLER)), + Error::::AlreadyHead + ); }); } #[test] fn control_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { // account with control (root) origin wants to only check 1 era per block. assert_ok!(FastUnstake::control(Origin::root(), 1_u32)); }); @@ -222,7 +192,7 @@ fn control_works() { #[test] fn control_must_be_control_origin() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { // account without control (root) origin wants to only check 1 era per block. assert_noop!(FastUnstake::control(Origin::signed(1), 1_u32), BadOrigin); }); @@ -230,11 +200,10 @@ fn control_must_be_control_origin() { #[test] fn unstake_paused_mid_election() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { // Initiate staking position - let (stash, ctrl) = mint_and_initiate_staking(); // Controller account registers for fast unstake. - FastUnstake::register_fast_unstake(Origin::signed(ctrl), Some(1_u32)); + FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)); todo!( "a dude is being unstaked, midway being checked, election happens, they are still not exposed, but a new era needs to be checked, therefore this unstake takes longer than From 1bc7835ad1f02ae29eef9d21e60c7aca47f191dd Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Mon, 29 Aug 2022 12:17:03 +0100 Subject: [PATCH 39/81] test unfinished --- frame/fast-unstake/src/lib.rs | 2 +- frame/fast-unstake/src/mock.rs | 4 --- frame/fast-unstake/src/tests.rs | 53 +++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 40f0f1480a75f..7c0c57f2915c7 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -313,7 +313,7 @@ pub mod pallet { /// in all use cases, even a single nominator cannot be unbonded in a single call. Multiple /// calls to this function are thus not needed. 2. We will only mark a staker #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))] - fn process_head(remaining_weight: Weight) -> Weight { + pub(crate) fn process_head(remaining_weight: Weight) -> Weight { let eras_to_check_per_block = ErasToCheckPerBlock::::get(); if eras_to_check_per_block.is_zero() { return T::DbWeight::get().reads(1) diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 678a730fce691..3c776b926afe5 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -351,9 +351,6 @@ impl ExtBuilder { self } - /// Utility function to mint a stash and controller account with 200 tokens - /// and start staking: stash bonds 100 tokens and nominates account 3. - /// returns the accounts [stash, ctrl] pub(crate) fn mint_and_initiate_staking() { // Mint accounts 1 and 2 with 200 tokens. for i in [STASH, CONTROLLER] { @@ -367,7 +364,6 @@ impl ExtBuilder { RewardDestination::Controller )); - // Stash nominates a validator // NOTE: not sure where this validator is coming from (not an actual validator). assert_ok!(Staking::nominate(Origin::signed(CONTROLLER), vec![3_u128])); } diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 67627f38bede5..c61714ee1fd9a 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -198,6 +198,59 @@ fn control_must_be_control_origin() { }); } +#[test] +fn process_head_tests() { + ExtBuilder::default().build_and_execute(|| { + // put 1 era per block + ErasToCheckPerBlock::::put(1); + + // TODO: go to block number after BondingDuration eras have passed. + // is there an accurate calculation for this? + run_to_block(50_000); + + // register for fast unstake + FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)); + assert_eq!(Queue::::get(STASH), Some(Some(1))); + + // process on idle + let remaining_weight = BlockWeights::get().max_block; + FastUnstake::on_idle(Zero::zero(), remaining_weight); + + // assert queue item has been moved to head + assert_eq!(Queue::::get(STASH), None); + + // assert head item present + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![0], maybe_pool_id: Some(1) }) + ); + + // run on_idle until BondingDuration - 1 + let bond_duration: u64 = BondingDuration::get().into(); + let next_block: u64 = System::block_number() + 1; + let last_block: u64 = next_block + bond_duration - 2; + + // checking blocks to loop... + assert_eq!(next_block, 50001); + assert_eq!(last_block, 50002); + + for i in next_block..last_block { + run_to_block(i); + FastUnstake::on_idle(i.into(), remaining_weight); + } + + // check head item still exists on last era, all eras checked - last + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![0], maybe_pool_id: Some(1) }) + ); + + // run on_idle again to process last era for head item, should be fully processed + run_to_block(System::block_number() + 1); + assert_eq!(Head::::get(), None); + }); +} + #[test] fn unstake_paused_mid_election() { ExtBuilder::default().build_and_execute(|| { From 818dabc59215ac4694455036e5d6cfaf6176a4aa Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 29 Aug 2022 16:56:35 +0430 Subject: [PATCH 40/81] cleanup and add some tests --- frame/fast-unstake/src/lib.rs | 4 +- frame/fast-unstake/src/mock.rs | 141 ++++++--------- frame/fast-unstake/src/tests.rs | 292 +++++++++++++++++++++++++------- 3 files changed, 286 insertions(+), 151 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 7c0c57f2915c7..5f1c66820f0ca 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -166,7 +166,7 @@ pub mod pallet { /// Points the `AccountId` wishing to unstake to the optional `PoolId` they wish to join /// thereafter. #[pallet::storage] - pub type Queue = StorageMap<_, Twox64Concat, T::AccountId, Option>; + pub type Queue = CountedStorageMap<_, Twox64Concat, T::AccountId, Option>; /// Number of eras to check per block. /// @@ -211,7 +211,7 @@ pub mod pallet { #[pallet::hooks] impl Hooks for Pallet { - fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight { + fn on_idle(_: T::BlockNumber, remaining_weight: Weight) -> Weight { Self::process_head(remaining_weight) } } diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 3c776b926afe5..5f1c938389a52 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -29,15 +29,16 @@ use sp_runtime::{ FixedU128, }; -use frame_system::RawOrigin; +use frame_system::{Account, RawOrigin}; use pallet_nomination_pools::{BondedPools, LastPoolId, PoolId, PoolState}; -use pallet_staking::RewardDestination; +use pallet_staking::{Exposure, IndividualExposure, RewardDestination}; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; pub type AccountId = u128; pub type AccountIndex = u32; pub type BlockNumber = u64; pub type Balance = u128; +pub type T = Runtime; parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = @@ -222,59 +223,31 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: frame_system::{Pallet, Call, Event}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, - Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, - FastUnstake: fast_unstake::{Pallet, Call, Storage, Event}, + System: frame_system, + Timestamp: pallet_timestamp, + Balances: pallet_balances, + Staking: pallet_staking, + Pools: pallet_nomination_pools, + FastUnstake: fast_unstake, } ); -pub fn new_test_ext() -> sp_io::TestExternalities { - sp_tracing::try_init_simple(); - let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let _ = pallet_nomination_pools::GenesisConfig:: { - min_join_bond: 2, - min_create_bond: 2, - max_pools: Some(3), - max_members_per_pool: Some(5), - max_members: Some(3 * 5), - } - .assimilate_storage(&mut storage) - .unwrap(); - - let _ = pallet_balances::GenesisConfig:: { - balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)], - } - .assimilate_storage(&mut storage) - .unwrap(); - - let mut ext = sp_io::TestExternalities::from(storage); - - ext.execute_with(|| { - // for events to be deposited. - frame_system::Pallet::::set_block_number(1); - - // set some limit for nominations. - assert_ok!(Staking::set_staking_configs( - Origin::root(), - pallet_staking::ConfigOp::Set(10), // minimum nominator bond - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - )); - }); - - ext -} - parameter_types! { static ObservedEventsPools: usize = 0; static ObservedEventsStaking: usize = 0; static ObservedEventsBalances: usize = 0; + static FastUnstakeEvents: u32 = 0; +} + +pub(crate) fn fast_unstake_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::FastUnstake(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = FastUnstakeEvents::get(); + FastUnstakeEvents::set(events.len() as u32); + events.into_iter().skip(already_seen as usize).collect() } pub(crate) fn pool_events_since_last_call() -> Vec> { @@ -351,23 +324,6 @@ impl ExtBuilder { self } - pub(crate) fn mint_and_initiate_staking() { - // Mint accounts 1 and 2 with 200 tokens. - for i in [STASH, CONTROLLER] { - let _ = Balances::make_free_balance_be(&i, 200); - } - // stash bonds 200 tokens with controller. - assert_ok!(Staking::bond( - Origin::signed(STASH), - CONTROLLER, - 100, - RewardDestination::Controller - )); - - // NOTE: not sure where this validator is coming from (not an actual validator). - assert_ok!(Staking::nominate(Origin::signed(CONTROLLER), vec![3_u128])); - } - pub(crate) fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = @@ -389,7 +345,31 @@ impl ExtBuilder { frame_system::Pallet::::set_block_number(1); // initial staking setup with some accounts 1 and 2 - Self::mint_and_initiate_staking(); + Balances::make_free_balance_be(&STASH, 200); + Balances::make_free_balance_be(&CONTROLLER, 200); + assert_ok!(Staking::bond( + Origin::signed(STASH), + CONTROLLER, + 100, + RewardDestination::Controller + )); + assert_ok!(Staking::nominate(Origin::signed(CONTROLLER), vec![3_u128])); + + // KIAN stuff + let validators = 32 as AccountId; + let nominators = 4 as AccountId; + for era in 0..(BondingDuration::get()) { + (100..100 + validators) + .map(|v| { + let others = (1000..(1000 + nominators)) + .map(|n| IndividualExposure { who: n, value: 0 as Balance }) + .collect::>(); + (v, Exposure { total: 0, own: 0, others }) + }) + .for_each(|(validator, exposure)| { + pallet_staking::ErasStakers::::insert(era, validator, exposure); + }); + } // make a pool let amount_to_bond = MinJoinBondConfig::get(); @@ -413,15 +393,6 @@ impl ExtBuilder { } } -pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) { - BondedPools::::try_mutate(pool_id, |maybe_bonded_pool| { - maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { - bonded_pool.state = state; - }) - }) - .unwrap() -} - pub(crate) fn run_to_block(n: u64) { let current_block = System::block_number(); assert!(n > current_block); @@ -430,7 +401,9 @@ pub(crate) fn run_to_block(n: u64) { Staking::on_finalize(System::block_number()); Pools::on_finalize(System::block_number()); FastUnstake::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + Balances::on_initialize(System::block_number()); Staking::on_initialize(System::block_number()); Pools::on_initialize(System::block_number()); @@ -438,17 +411,9 @@ pub(crate) fn run_to_block(n: u64) { } } -parameter_types! { - storage FastUnstakeEvents: u32 = 0; -} - -pub(crate) fn fast_unstake_events_since_last_call() -> Vec> { - let events = System::events() - .into_iter() - .map(|r| r.event) - .filter_map(|e| if let Event::FastUnstake(inner) = e { Some(inner) } else { None }) - .collect::>(); - let already_seen = FastUnstakeEvents::get(); - FastUnstakeEvents::set(&(events.len() as u32)); - events.into_iter().skip(already_seen as usize).collect() +pub fn assert_unstaked(stash: &AccountId) { + assert!(!pallet_staking::Bonded::::contains_key(stash)); + assert!(!pallet_staking::Payee::::contains_key(stash)); + assert!(!pallet_staking::Validators::::contains_key(stash)); + assert!(!pallet_staking::Nominators::::contains_key(stash)); } diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index c61714ee1fd9a..4dafdcac7d827 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -31,7 +31,7 @@ use pallet_balances::Error as BalancesError; use pallet_nomination_pools::{ BondedPool, BondedPools, LastPoolId, PoolId, PoolRoles, RewardPools, }; -use pallet_staking::RewardDestination; +use pallet_staking::{CurrentEra, RewardDestination}; use sp_runtime::{ assert_eq_error_rate, @@ -45,9 +45,6 @@ use sp_staking::{ use sp_std::prelude::*; use substrate_test_utils::assert_eq_uvec; -pub const DEFAULT_ROLES: PoolRoles = - PoolRoles { depositor: 10, root: Some(900), nominator: Some(901), state_toggler: Some(902) }; - #[test] fn test_setup_works() { ExtBuilder::default().build_and_execute(|| { @@ -119,7 +116,7 @@ fn cannot_register_if_has_unlocking_chunks() { ExtBuilder::default().build_and_execute(|| { // Start unbonding half of staked tokens assert_ok!(Staking::unbond(Origin::signed(CONTROLLER), 50_u128)); - // Cannot regsiter for fast unstake with unlock chunks active + // Cannot register for fast unstake with unlock chunks active assert_noop!( FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)), Error::::NotFullyBonded @@ -131,7 +128,7 @@ fn cannot_register_if_has_unlocking_chunks() { fn deregister_works() { ExtBuilder::default().build_and_execute(|| { // Controller account registers for fast unstake. - FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); // Controller then changes mind and deregisters. assert_ok!(FastUnstake::deregister(Origin::signed(CONTROLLER))); // Ensure stash no longer exists in the queue. @@ -143,7 +140,7 @@ fn deregister_works() { fn cannot_deregister_if_not_controller() { ExtBuilder::default().build_and_execute(|| { // Controller account registers for fast unstake. - FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); // Stash tries to deregister. assert_noop!( FastUnstake::deregister(Origin::signed(STASH)), @@ -167,7 +164,7 @@ fn cannot_deregister_if_not_queued() { fn cannot_deregister_already_head() { ExtBuilder::default().build_and_execute(|| { // Controller attempts to regsiter, should fail - FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); // Insert some Head item for stash. Head::::put(UnstakeRequest { stash: STASH.clone(), @@ -198,69 +195,242 @@ fn control_must_be_control_origin() { }); } -#[test] -fn process_head_tests() { - ExtBuilder::default().build_and_execute(|| { - // put 1 era per block - ErasToCheckPerBlock::::put(1); +mod on_idle { + use super::*; - // TODO: go to block number after BondingDuration eras have passed. - // is there an accurate calculation for this? - run_to_block(50_000); + #[test] + fn early_exit() { + todo!("remaining_weight = 0 should do NOTHING"); + } - // register for fast unstake - FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)); - assert_eq!(Queue::::get(STASH), Some(Some(1))); + #[test] + fn respects_weight() { + // TODO: KIAN + todo!("ErasToCheckPerBlock is 5, but the remaining weight is such that we can only process 2"); + } - // process on idle - let remaining_weight = BlockWeights::get().max_block; - FastUnstake::on_idle(Zero::zero(), remaining_weight); - // assert queue item has been moved to head - assert_eq!(Queue::::get(STASH), None); + #[test] + fn successful_multi_queue() { + todo!("multiple stakers in queue, all leaving") + } - // assert head item present - assert_eq!( - Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![0], maybe_pool_id: Some(1) }) - ); + #[test] + fn successful_unstake_without_pool_join() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); - // run on_idle until BondingDuration - 1 - let bond_duration: u64 = BondingDuration::get().into(); - let next_block: u64 = System::block_number() + 1; - let last_block: u64 = next_block + bond_duration - 2; + // register for fast unstake + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), None)); + assert_eq!(Queue::::get(STASH), Some(None)); - // checking blocks to loop... - assert_eq!(next_block, 50001); - assert_eq!(last_block, 50002); + // process on idle + let remaining_weight = BlockWeights::get().max_block; + FastUnstake::on_idle(Zero::zero(), remaining_weight); - for i in next_block..last_block { - run_to_block(i); - FastUnstake::on_idle(i.into(), remaining_weight); - } + // assert queue item has been moved to head + assert_eq!(Queue::::get(STASH), None); - // check head item still exists on last era, all eras checked - last - assert_eq!( - Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![0], maybe_pool_id: Some(1) }) - ); + // assert head item present + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![3, 2, 1, 0], maybe_pool_id: None }) + ); - // run on_idle again to process last era for head item, should be fully processed - run_to_block(System::block_number() + 1); - assert_eq!(Head::::get(), None); - }); + let remaining_weight = BlockWeights::get().max_block; + FastUnstake::on_idle(Zero::zero(), remaining_weight); + + assert_eq!(Head::::get(), None,); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::Checked { stash: 1, eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 1, maybe_pool_id: None, result: Ok(()) } + ] + ); + assert_unstaked(&STASH); + }); + } + + #[test] + fn successful_unstake_joining_bad_pool() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // register for fast unstake + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(0))); + assert_eq!(Queue::::get(STASH), Some(Some(0))); + + // process on idle + let remaining_weight = BlockWeights::get().max_block; + FastUnstake::on_idle(Zero::zero(), remaining_weight); + + // assert queue item has been moved to head + assert_eq!(Queue::::get(STASH), None); + + // assert head item present + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stash: 1, + checked: vec![3, 2, 1, 0], + maybe_pool_id: Some(0) + }) + ); + + let remaining_weight = BlockWeights::get().max_block; + FastUnstake::on_idle(Zero::zero(), remaining_weight); + + assert_eq!(Head::::get(), None,); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::Checked { stash: 1, eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 1, maybe_pool_id: Some(0), result: Ok(()) } + ] + ); + assert_unstaked(&STASH); + }); + } + + #[test] + fn successful_unstake_all_eras_per_block() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // register for fast unstake + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); + assert_eq!(Queue::::get(STASH), Some(Some(1))); + + // process on idle + let remaining_weight = BlockWeights::get().max_block; + FastUnstake::on_idle(Zero::zero(), remaining_weight); + + // assert queue item has been moved to head + assert_eq!(Queue::::get(STASH), None); + + // assert head item present + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stash: 1, + checked: vec![3, 2, 1, 0], + maybe_pool_id: Some(1) + }) + ); + + let remaining_weight = BlockWeights::get().max_block; + FastUnstake::on_idle(Zero::zero(), remaining_weight); + + assert_eq!(Head::::get(), None,); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::Checked { stash: 1, eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 1, maybe_pool_id: Some(1), result: Ok(()) } + ] + ); + assert_unstaked(&STASH); + assert!(pallet_nomination_pools::PoolMembers::::contains_key(&STASH)); + }); + } + + #[test] + fn successful_unstake_one_era_per_block() { + ExtBuilder::default().build_and_execute(|| { + // put 1 era per block + ErasToCheckPerBlock::::put(1); + CurrentEra::::put(BondingDuration::get()); + + // register for fast unstake + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); + assert_eq!(Queue::::get(STASH), Some(Some(1))); + + // process on idle + let remaining_weight = BlockWeights::get().max_block; + FastUnstake::on_idle(Zero::zero(), remaining_weight); + + // assert queue item has been moved to head + assert_eq!(Queue::::get(STASH), None); + + // assert head item present + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![3], maybe_pool_id: Some(1) }) + ); + + let remaining_weight = BlockWeights::get().max_block; + FastUnstake::on_idle(Zero::zero(), remaining_weight); + + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![3, 2], maybe_pool_id: Some(1) }) + ); + + let remaining_weight = BlockWeights::get().max_block; + FastUnstake::on_idle(Zero::zero(), remaining_weight); + + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![3, 2, 1], maybe_pool_id: Some(1) }) + ); + + let remaining_weight = BlockWeights::get().max_block; + FastUnstake::on_idle(Zero::zero(), remaining_weight); + + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stash: 1, + checked: vec![3, 2, 1, 0], + maybe_pool_id: Some(1) + }) + ); + + let remaining_weight = BlockWeights::get().max_block; + FastUnstake::on_idle(Zero::zero(), remaining_weight); + + assert_eq!(Head::::get(), None,); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::Checked { stash: 1, eras: vec![3] }, + Event::Checked { stash: 1, eras: vec![2] }, + Event::Checked { stash: 1, eras: vec![1] }, + Event::Checked { stash: 1, eras: vec![0] }, + Event::Unstaked { stash: 1, maybe_pool_id: Some(1), result: Ok(()) } + ] + ); + assert_unstaked(&STASH); + assert!(pallet_nomination_pools::PoolMembers::::contains_key(&STASH)); + }); + } + + #[test] + fn unstake_paused_mid_election() { + // TODO: KIAN + ExtBuilder::default().build_and_execute(|| { + // Initiate staking position + // Controller account registers for fast unstake. + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); + // todo!( + // "a dude is being unstaked, midway being checked, election happens, they are still not + // exposed, but a new era needs to be checked, therefore this unstake takes longer than + // expected." + // ) + }); + } } -#[test] -fn unstake_paused_mid_election() { - ExtBuilder::default().build_and_execute(|| { - // Initiate staking position - // Controller account registers for fast unstake. - FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)); - todo!( - "a dude is being unstaked, midway being checked, election happens, they are still not - exposed, but a new era needs to be checked, therefore this unstake takes longer than - expected." - ) - }); + +mod signed_extension { + use super::*; + // TODO: } From 239f45fae7c68ccfe97ab18c58a0d9d1ba51d352 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 30 Aug 2022 12:02:38 +0100 Subject: [PATCH 41/81] add test successful_multi_queue --- frame/fast-unstake/src/mock.rs | 14 ++++++++ frame/fast-unstake/src/tests.rs | 62 +++++++++++++++++++++++++++++---- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 5f1c938389a52..1259b35a30241 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -286,6 +286,8 @@ impl Default for ExtBuilder { pub(crate) const STASH: u128 = 1; pub(crate) const CONTROLLER: u128 = 2; +pub(crate) const STASH_2: u128 = 3; +pub(crate) const CONTROLLER_2: u128 = 4; impl ExtBuilder { // Add members to pool 0. @@ -347,13 +349,25 @@ impl ExtBuilder { // initial staking setup with some accounts 1 and 2 Balances::make_free_balance_be(&STASH, 200); Balances::make_free_balance_be(&CONTROLLER, 200); + Balances::make_free_balance_be(&STASH_2, 200); + Balances::make_free_balance_be(&CONTROLLER_2, 200); + assert_ok!(Staking::bond( Origin::signed(STASH), CONTROLLER, 100, RewardDestination::Controller )); + + assert_ok!(Staking::bond( + Origin::signed(STASH_2), + CONTROLLER_2, + 100, + RewardDestination::Stash + )); + assert_ok!(Staking::nominate(Origin::signed(CONTROLLER), vec![3_u128])); + assert_ok!(Staking::nominate(Origin::signed(CONTROLLER_2), vec![3_u128])); // KIAN stuff let validators = 32 as AccountId; diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 4dafdcac7d827..de24cde8a997a 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -36,7 +36,7 @@ use pallet_staking::{CurrentEra, RewardDestination}; use sp_runtime::{ assert_eq_error_rate, traits::{BadOrigin, Dispatchable, Zero}, - Perbill, Percent, + DispatchError, ModuleError, Perbill, Percent, }; use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, @@ -206,13 +206,56 @@ mod on_idle { #[test] fn respects_weight() { // TODO: KIAN - todo!("ErasToCheckPerBlock is 5, but the remaining weight is such that we can only process 2"); + todo!( + "ErasToCheckPerBlock is 5, but the remaining weight is such that we can only process 2" + ); } - #[test] fn successful_multi_queue() { - todo!("multiple stakers in queue, all leaving") + ExtBuilder::default().build_and_execute(|| { + let max_block_weight = BlockWeights::get().max_block; + + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // register multi accounts for fast unstake + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1))); + assert_eq!(Queue::::get(STASH), Some(Some(1))); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER_2), Some(1))); + assert_eq!(Queue::::get(STASH_2), Some(Some(1))); + + // assert 2 queue items are in Queue & None in Head to start with + assert_eq!(Queue::::count(), 2); + assert_eq!(Head::::get(), None); + + // process on idle & run to next block + FastUnstake::on_idle(System::block_number(), max_block_weight); + run_to_block(System::block_number() + 1); + + // make sure there is some Queue item left in head + assert_ne!(Head::::get(), None); + assert_eq!(Queue::::count(), 1); + + // running on_idle again + FastUnstake::on_idle(System::block_number(), max_block_weight); + + // Head & Queue should now be empty + assert_eq!(Head::::get(), None); + assert_eq!(Queue::::count(), 0); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::Checked { stash: 1, eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 1, maybe_pool_id: Some(1), result: Ok(()) }, + Event::Checked { stash: 3, eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 3, maybe_pool_id: Some(1), result: Ok(()) }, + ] + ); + assert_unstaked(&STASH); + assert_unstaked(&STASH_2); + }); } #[test] @@ -290,7 +333,15 @@ mod on_idle { fast_unstake_events_since_last_call(), vec![ Event::Checked { stash: 1, eras: vec![3, 2, 1, 0] }, - Event::Unstaked { stash: 1, maybe_pool_id: Some(0), result: Ok(()) } + Event::Unstaked { + stash: 1, + maybe_pool_id: Some(0), + result: Err(DispatchError::Module(ModuleError { + index: 4, + error: [0, 0, 0, 0], + message: None + })) + } ] ); assert_unstaked(&STASH); @@ -429,7 +480,6 @@ mod on_idle { } } - mod signed_extension { use super::*; // TODO: From 141798de70d73588ba84fa87721505d7587a8f68 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 30 Aug 2022 12:02:45 +0100 Subject: [PATCH 42/81] comment --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 5f1c66820f0ca..23879390f9de0 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -320,7 +320,7 @@ pub mod pallet { } // NOTE: here we're assuming that the number of validators has only ever increased, - // meaning that the number of exposures to check is either this per era, or less.s + // meaning that the number of exposures to check is either this per era, or less. let validator_count = pallet_staking::ValidatorCount::::get(); // determine the number of eras to check. This is based on both `ErasToCheckPerBlock` From 2777d238d77162f96720075b9ff0b6670e2f61f0 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 30 Aug 2022 12:05:30 +0100 Subject: [PATCH 43/81] rm Head check --- frame/fast-unstake/src/tests.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index de24cde8a997a..02bd16355fb2d 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -233,8 +233,7 @@ mod on_idle { FastUnstake::on_idle(System::block_number(), max_block_weight); run_to_block(System::block_number() + 1); - // make sure there is some Queue item left in head - assert_ne!(Head::::get(), None); + // make sure there is some Queue item remaining assert_eq!(Queue::::count(), 1); // running on_idle again From 642fb02d691750e94f258c640bd9f57108f938a9 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 30 Aug 2022 12:06:57 +0100 Subject: [PATCH 44/81] add TODO --- frame/fast-unstake/src/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 02bd16355fb2d..465400ab6128e 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -234,6 +234,7 @@ mod on_idle { run_to_block(System::block_number() + 1); // make sure there is some Queue item remaining + // TODO: check Head state at this stage - should be empty? assert_eq!(Queue::::count(), 1); // running on_idle again From f34d43ed70082d9ca7326c674ee784f991366c37 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 30 Aug 2022 15:11:01 +0100 Subject: [PATCH 45/81] complete successful_multi_queue --- frame/fast-unstake/src/tests.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 465400ab6128e..8c95a86d58e44 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -229,16 +229,25 @@ mod on_idle { assert_eq!(Queue::::count(), 2); assert_eq!(Head::::get(), None); - // process on idle & run to next block + // process on idle and check eras for next Queue item FastUnstake::on_idle(System::block_number(), max_block_weight); run_to_block(System::block_number() + 1); - // make sure there is some Queue item remaining - // TODO: check Head state at this stage - should be empty? + // process on idle & let go of current Head + FastUnstake::on_idle(System::block_number(), max_block_weight); + run_to_block(System::block_number() + 1); + + // confirm Head / Queue items remaining + assert_eq!(Head::::get(), None); assert_eq!(Queue::::count(), 1); - // running on_idle again + // process on idle and check eras for next Queue item FastUnstake::on_idle(System::block_number(), max_block_weight); + run_to_block(System::block_number() + 1); + + // process on idle & let go of current Head + FastUnstake::on_idle(System::block_number(), max_block_weight); + run_to_block(System::block_number() + 1); // Head & Queue should now be empty assert_eq!(Head::::get(), None); From 058dfe57d8ea4bdc209dfe793f7fab17f516ed91 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 30 Aug 2022 15:23:04 +0100 Subject: [PATCH 46/81] + test early_exit --- frame/fast-unstake/src/tests.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 8c95a86d58e44..38b85af071d47 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -200,7 +200,21 @@ mod on_idle { #[test] fn early_exit() { - todo!("remaining_weight = 0 should do NOTHING"); + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // set up Queue item + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1))); + assert_eq!(Queue::::get(STASH), Some(Some(1))); + + // call on_idle with no remaining weight + FastUnstake::on_idle(System::block_number(), 0); + + // assert nothing changed in Queue and Head + assert_eq!(Head::::get(), None); + assert_eq!(Queue::::get(STASH), Some(Some(1))); + }); } #[test] From 5d78bf6b544a5d29b2a4f2fd77bd91903eab2f61 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 7 Sep 2022 12:46:55 +0100 Subject: [PATCH 47/81] =?UTF-8?q?fix=20a=20lot=20of=20things=20above=20the?= =?UTF-8?q?=20beautiful=20atlantic=20ocean=20=F0=9F=8C=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frame/fast-unstake/src/benchmarking.rs | 17 +- frame/fast-unstake/src/lib.rs | 62 ++- frame/fast-unstake/src/mock.rs | 246 +++++------- frame/fast-unstake/src/tests.rs | 518 +++++++++++++++++-------- frame/fast-unstake/src/weights.rs | 210 ++++++++++ 5 files changed, 684 insertions(+), 369 deletions(-) create mode 100644 frame/fast-unstake/src/weights.rs diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index 93c7d499c4440..2c7a064428afb 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -68,8 +68,6 @@ fn fund_and_bond_account(account: &T::AccountId) { )); } -/// All events of the `Balances` pallet. -// TODO: reused this in the tests mock. pub(crate) fn fast_unstake_events() -> Vec> { frame_system::Pallet::::events() .into_iter() @@ -145,7 +143,7 @@ benchmarks! { on_idle_full_block::(); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: who.clone(), checked: vec![0], maybe_pool_id: Some(1) }) + Some(UnstakeRequest { stash: who.clone(), checked: vec![0], maybe_pool_id: Some(pool_id) }) ); } : { @@ -198,9 +196,13 @@ benchmarks! { register_fast_unstake { let who = create_unexposed_nominator::(); whitelist_account!(who); + assert_eq!(Queue::::count(), 0); + } :_(RawOrigin::Signed(who.clone()), None) - verify {} + verify { + assert_eq!(Queue::::count(), 1); + } deregister { let who = create_unexposed_nominator::(); @@ -208,10 +210,13 @@ benchmarks! { RawOrigin::Signed(who.clone()).into(), None )); + assert_eq!(Queue::::count(), 1); whitelist_account!(who); } :_(RawOrigin::Signed(who.clone())) - verify {} + verify { + assert_eq!(Queue::::count(), 0); + } control { let origin = ::ControlOrigin::successful_origin(); @@ -219,5 +224,5 @@ benchmarks! { : _(origin, 128) verify {} - // impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test) + impl_benchmark_test_suite!(Pallet, crate::mock::ExtBuilder::default().build(), crate::mock::Runtime) } diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 23879390f9de0..18b116aa01c55 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -44,15 +44,16 @@ #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking; - pub use pallet::*; #[cfg(test)] mod mock; #[cfg(test)] mod tests; +// NOTE: enable benchmarking in tests as well. +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; pub const LOG_TARGET: &'static str = "runtime::fast-unstake"; @@ -85,32 +86,7 @@ pub mod pallet { }; use sp_staking::EraIndex; use sp_std::{prelude::*, vec::Vec}; - - pub trait WeightInfo { - fn register_fast_unstake() -> Weight; - fn deregister() -> Weight; - fn control() -> Weight; - fn on_idle_unstake() -> Weight; - fn on_idle_check(v: u32, e: u32) -> Weight; - } - - impl WeightInfo for () { - fn register_fast_unstake() -> Weight { - 0 - } - fn deregister() -> Weight { - 0 - } - fn control() -> Weight { - 0 - } - fn on_idle_unstake() -> Weight { - 0 - } - fn on_idle_check(_: u32, _: u32) -> Weight { - 0 - } - } + use weights::WeightInfo; type BalanceOf = <::Currency as Currency< ::AccountId, @@ -325,18 +301,18 @@ pub mod pallet { // determine the number of eras to check. This is based on both `ErasToCheckPerBlock` // and `remaining_weight` passed on to us from the runtime executive. - #[cfg(feature = "runtime-benchmarks")] - let final_eras_to_check = eras_to_check_per_block; - #[cfg(not(feature = "runtime-benchmarks"))] let final_eras_to_check = { let worse_weight = |v, u| { - ::WeightInfo::on_idle_check(v, u) + ::WeightInfo::on_idle_check(v * u) .max(::WeightInfo::on_idle_unstake()) }; let mut try_eras_to_check = eras_to_check_per_block; - while worse_weight(validator_count, try_eras_to_check) > remaining_weight { + while dbg!(worse_weight(validator_count, try_eras_to_check)) > + dbg!(remaining_weight) + { try_eras_to_check.saturating_dec(); if try_eras_to_check.is_zero() { + log!(debug, "early existing because try_eras_to_check is zero"); return T::DbWeight::get().reads(1) } } @@ -371,7 +347,13 @@ pub mod pallet { Some(head) => head, }; - log!(debug, "checking {:?}", stash); + log!( + debug, + "checking {:?}, final_eras_to_check = {:?}, remaining_weight = {:?}", + stash, + final_eras_to_check, + remaining_weight + ); // the range that we're allowed to check in this round. let current_era = pallet_staking::CurrentEra::::get().unwrap_or_default(); @@ -438,6 +420,13 @@ pub mod pallet { }; let result = unstake_result.and(pool_stake_result); + log!( + info, + "unstaked {:?}, maybe_pool {:?}, outcome: {:?}", + stash, + maybe_pool_id, + result + ); Self::deposit_event(Event::::Unstaked { stash, maybe_pool_id, result }); ::WeightInfo::on_idle_unstake() @@ -469,6 +458,7 @@ pub mod pallet { &mut Default::default(), current_era, ); + log!(info, "slashed {:?} by {:?}", stash, amount); Self::deposit_event(Event::::Slashed { stash, amount }); } else { // Not exposed in these two eras. @@ -477,7 +467,7 @@ pub mod pallet { Self::deposit_event(Event::::Checked { stash, eras: eras_to_check }); } - ::WeightInfo::on_idle_check(validator_count, final_eras_to_check) + ::WeightInfo::on_idle_check(validator_count * eras_checked) } } diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 1259b35a30241..b81b69d7f2da0 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -15,13 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::*; use crate::{self as fast_unstake}; use frame_support::{ assert_ok, pallet_prelude::*, parameter_types, traits::{ConstU64, ConstU8, Currency}, + weights::constants::WEIGHT_PER_SECOND, PalletId, }; use sp_runtime::{ @@ -29,10 +29,9 @@ use sp_runtime::{ FixedU128, }; -use frame_system::{Account, RawOrigin}; -use pallet_nomination_pools::{BondedPools, LastPoolId, PoolId, PoolState}; -use pallet_staking::{Exposure, IndividualExposure, RewardDestination}; -use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; +use frame_system::RawOrigin; +use pallet_staking::{Exposure, IndividualExposure}; +use sp_std::prelude::*; pub type AccountId = u128; pub type AccountIndex = u32; @@ -42,7 +41,7 @@ pub type T = Runtime; parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(2u64 * WEIGHT_PER_SECOND); } impl frame_system::Config for Runtime { @@ -84,7 +83,7 @@ parameter_types! { } impl pallet_balances::Config for Runtime { - type MaxLocks = (); + type MaxLocks = ConstU32<128>; type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = Balance; @@ -109,14 +108,24 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; pub static BondingDuration: u32 = 3; - pub static MinJoinBondConfig: Balance = 2; pub static CurrentEra: u32 = 0; - pub storage BondedBalanceMap: BTreeMap = Default::default(); - pub storage UnbondingBalanceMap: BTreeMap = Default::default(); - #[derive(Clone, PartialEq)] - pub static MaxUnbonding: u32 = 8; - pub static StakingMinBond: Balance = 10; - pub storage Nominations: Option> = None; + pub static Ongoing: bool = false; +} + +pub struct MockElection; +impl frame_election_provider_support::ElectionProvider for MockElection { + type AccountId = AccountId; + type BlockNumber = BlockNumber; + type DataProvider = Staking; + type Error = (); + + fn ongoing() -> bool { + Ongoing::get() + } + + fn elect() -> Result, Self::Error> { + Err(()) + } } impl pallet_staking::Config for Runtime { @@ -138,8 +147,7 @@ impl pallet_staking::Config for Runtime { type NextNewSession = (); type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = (); - type ElectionProvider = - frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; + type ElectionProvider = MockElection; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; @@ -165,7 +173,7 @@ impl Convert for U256ToBalance { parameter_types! { pub const PostUnbondingPoolsWindow: u32 = 10; pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); - pub static MaxMetadataLen: u32 = 2; + pub static MaxMetadataLen: u32 = 10; pub static CheckLevel: u8 = 255; } @@ -185,25 +193,6 @@ impl pallet_nomination_pools::Config for Runtime { type PalletId = PoolsPalletId; } -pub struct FastUnstakeWeightInfo; -impl fast_unstake::WeightInfo for FastUnstakeWeightInfo { - fn register_fast_unstake() -> Weight { - 10 - } - fn deregister() -> Weight { - 5 - } - fn control() -> Weight { - 2 - } - fn on_idle_unstake() -> Weight { - 10 - } - fn on_idle_check(v: u32, e: u32) -> Weight { - 10 - } -} - parameter_types! { pub static SlashPerEra: u32 = 100; } @@ -212,7 +201,7 @@ impl fast_unstake::Config for Runtime { type Event = Event; type SlashPerEra = SlashPerEra; type ControlOrigin = frame_system::EnsureRoot; - type WeightInfo = FastUnstakeWeightInfo; + type WeightInfo = (); } type Block = frame_system::mocking::MockBlock; @@ -233,9 +222,6 @@ frame_support::construct_runtime!( ); parameter_types! { - static ObservedEventsPools: usize = 0; - static ObservedEventsStaking: usize = 0; - static ObservedEventsBalances: usize = 0; static FastUnstakeEvents: u32 = 0; } @@ -250,80 +236,36 @@ pub(crate) fn fast_unstake_events_since_last_call() -> Vec events.into_iter().skip(already_seen as usize).collect() } -pub(crate) fn pool_events_since_last_call() -> Vec> { - let events = System::events() - .into_iter() - .map(|r| r.event) - .filter_map(|e| if let Event::Pools(inner) = e { Some(inner) } else { None }) - .collect::>(); - let already_seen = ObservedEventsPools::get(); - ObservedEventsPools::set(events.len()); - events.into_iter().skip(already_seen).collect() -} - -pub(crate) fn staking_events_since_last_call() -> Vec> { - let events = System::events() - .into_iter() - .map(|r| r.event) - .filter_map(|e| if let Event::Staking(inner) = e { Some(inner) } else { None }) - .collect::>(); - let already_seen = ObservedEventsStaking::get(); - ObservedEventsStaking::set(events.len()); - events.into_iter().skip(already_seen).collect() -} - pub struct ExtBuilder { - members: Vec<(AccountId, Balance)>, - max_members: Option, - max_members_per_pool: Option, + stakers: Vec<(AccountId, AccountId, Balance)>, } impl Default for ExtBuilder { fn default() -> Self { - Self { members: Default::default(), max_members: Some(4), max_members_per_pool: Some(3) } + Self { stakers: vec![(1, 2, 100), (3, 4, 100), (5, 6, 100), (7, 8, 100), (9, 10, 100)] } } } -pub(crate) const STASH: u128 = 1; -pub(crate) const CONTROLLER: u128 = 2; -pub(crate) const STASH_2: u128 = 3; -pub(crate) const CONTROLLER_2: u128 = 4; +pub(crate) const VALIDATORS_PER_ERA: AccountId = 32; +pub(crate) const NOMINATORS_PER_VALIDATOR_PER_ERA: AccountId = 4; impl ExtBuilder { - // Add members to pool 0. - pub(crate) fn add_members(mut self, members: Vec<(AccountId, Balance)>) -> Self { - self.members = members; - self - } - - pub(crate) fn ed(self, ed: Balance) -> Self { - ExistentialDeposit::set(ed); - self - } - - pub(crate) fn min_bond(self, min: Balance) -> Self { - StakingMinBond::set(min); - self - } - - pub(crate) fn min_join_bond(self, min: Balance) -> Self { - MinJoinBondConfig::set(min); - self - } - - pub(crate) fn with_check(self, level: u8) -> Self { - CheckLevel::set(level); - self - } - - pub(crate) fn max_members(mut self, max: Option) -> Self { - self.max_members = max; - self - } - - pub(crate) fn max_members_per_pool(mut self, max: Option) -> Self { - self.max_members_per_pool = max; - self + pub(crate) fn register_stakers_for_era(era: u32, validators: AccountId, nominators: AccountId) { + // validators are prefixed with 100 and nominators with 1000 to prevent conflict. Make sure + // all the other accounts used in tests are below 100. Also ensure here that we don't + // overlap. + assert!(100 + validators < 10000); + + (100..100 + validators) + .map(|v| { + let others = (1000..(1000 + nominators)) + .map(|n| IndividualExposure { who: n, value: 0 as Balance }) + .collect::>(); + (v, Exposure { total: 0, own: 0, others }) + }) + .for_each(|(validator, exposure)| { + pallet_staking::ErasStakers::::insert(era, validator, exposure); + }); } pub(crate) fn build(self) -> sp_io::TestExternalities { @@ -331,12 +273,30 @@ impl ExtBuilder { let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let _ = pallet_nomination_pools::GenesisConfig:: { - min_join_bond: MinJoinBondConfig::get(), - min_create_bond: 2, - max_pools: Some(2), - max_members_per_pool: self.max_members_per_pool, - max_members: self.max_members, + // create one default pool. + let _ = pallet_nomination_pools::GenesisConfig:: { ..Default::default() } + .assimilate_storage(&mut storage); + + let _ = pallet_balances::GenesisConfig:: { + balances: self + .stakers + .clone() + .into_iter() + .map(|(stash, _, balance)| (stash, balance * 2)) + .chain( + self.stakers.clone().into_iter().map(|(_, ctrl, balance)| (ctrl, balance * 2)), + ) + .collect::>(), + } + .assimilate_storage(&mut storage); + + let _ = pallet_staking::GenesisConfig:: { + stakers: self + .stakers + .into_iter() + .map(|(x, y, z)| (x, y, z, pallet_staking::StakerStatus::Nominator(vec![42]))) + .collect::>(), + ..Default::default() } .assimilate_storage(&mut storage); @@ -346,55 +306,21 @@ impl ExtBuilder { // for events to be deposited. frame_system::Pallet::::set_block_number(1); - // initial staking setup with some accounts 1 and 2 - Balances::make_free_balance_be(&STASH, 200); - Balances::make_free_balance_be(&CONTROLLER, 200); - Balances::make_free_balance_be(&STASH_2, 200); - Balances::make_free_balance_be(&CONTROLLER_2, 200); - - assert_ok!(Staking::bond( - Origin::signed(STASH), - CONTROLLER, - 100, - RewardDestination::Controller - )); - - assert_ok!(Staking::bond( - Origin::signed(STASH_2), - CONTROLLER_2, - 100, - RewardDestination::Stash - )); - - assert_ok!(Staking::nominate(Origin::signed(CONTROLLER), vec![3_u128])); - assert_ok!(Staking::nominate(Origin::signed(CONTROLLER_2), vec![3_u128])); - - // KIAN stuff - let validators = 32 as AccountId; - let nominators = 4 as AccountId; - for era in 0..(BondingDuration::get()) { - (100..100 + validators) - .map(|v| { - let others = (1000..(1000 + nominators)) - .map(|n| IndividualExposure { who: n, value: 0 as Balance }) - .collect::>(); - (v, Exposure { total: 0, own: 0, others }) - }) - .for_each(|(validator, exposure)| { - pallet_staking::ErasStakers::::insert(era, validator, exposure); - }); + for era in 0..=(BondingDuration::get()) { + Self::register_stakers_for_era( + era, + VALIDATORS_PER_ERA, + NOMINATORS_PER_VALIDATOR_PER_ERA, + ); } + // because we read this value as a measure of how many validators we have. + pallet_staking::ValidatorCount::::put(VALIDATORS_PER_ERA as u32); + // make a pool - let amount_to_bond = MinJoinBondConfig::get(); + let amount_to_bond = Pools::depositor_min_bond(); Balances::make_free_balance_be(&10, amount_to_bond * 5); assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902)); - - let last_pool = LastPoolId::::get(); - for (account_id, bonded) in self.members { - Balances::make_free_balance_be(&account_id, bonded * 2); - assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, last_pool)); - } }); ext } @@ -402,12 +328,12 @@ impl ExtBuilder { pub fn build_and_execute(self, test: impl FnOnce() -> ()) { self.build().execute_with(|| { test(); - Pools::sanity_checks(CheckLevel::get()).unwrap(); + // TODO: sanity check, if any }) } } -pub(crate) fn run_to_block(n: u64) { +pub(crate) fn run_to_block(n: u64, on_idle: bool) { let current_block = System::block_number(); assert!(n > current_block); while System::block_number() < n { @@ -422,9 +348,17 @@ pub(crate) fn run_to_block(n: u64) { Staking::on_initialize(System::block_number()); Pools::on_initialize(System::block_number()); FastUnstake::on_initialize(System::block_number()); + if on_idle { + FastUnstake::on_idle(System::block_number(), BlockWeights::get().max_block); + } } } +pub(crate) fn next_block(on_idle: bool) { + let current = System::block_number(); + run_to_block(current + 1, on_idle); +} + pub fn assert_unstaked(stash: &AccountId) { assert!(!pallet_staking::Bonded::::contains_key(stash)); assert!(!pallet_staking::Payee::::contains_key(stash)); diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 38b85af071d47..8ee2b1abf03ed 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -18,40 +18,25 @@ //! Tests for pallet-fast-unstake. use super::*; -use crate::{mock::*, Event}; -use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support}; -use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, bounded_vec, - dispatch::WithPostDispatchInfo, - pallet_prelude::*, - traits::{Currency, Get, ReservableCurrency}, - weights::{extract_actual_weight, GetDispatchInfo}, -}; -use pallet_balances::Error as BalancesError; -use pallet_nomination_pools::{ - BondedPool, BondedPools, LastPoolId, PoolId, PoolRoles, RewardPools, -}; -use pallet_staking::{CurrentEra, RewardDestination}; +use crate::{mock::*, weights::WeightInfo, Event}; +use frame_support::{assert_noop, assert_ok, pallet_prelude::*, traits::Currency}; +use pallet_nomination_pools::{BondedPools, LastPoolId, RewardPools}; +use pallet_staking::CurrentEra; use sp_runtime::{ - assert_eq_error_rate, - traits::{BadOrigin, Dispatchable, Zero}, - DispatchError, ModuleError, Perbill, Percent, -}; -use sp_staking::{ - offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - SessionIndex, StakingInterface, + traits::BadOrigin, + DispatchError, ModuleError, }; +use sp_staking::StakingInterface; use sp_std::prelude::*; -use substrate_test_utils::assert_eq_uvec; #[test] fn test_setup_works() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(BondedPools::::count(), 1); - assert_eq!(RewardPools::::count(), 1); + assert_eq!(BondedPools::::count(), 1); + assert_eq!(RewardPools::::count(), 1); assert_eq!(Staking::bonding_duration(), 3); - let last_pool = LastPoolId::::get(); + let last_pool = LastPoolId::::get(); assert_eq!(last_pool, 1); }); } @@ -60,9 +45,9 @@ fn test_setup_works() { fn register_works() { ExtBuilder::default().build_and_execute(|| { // Controller account registers for fast unstake. - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); // Ensure stash is in the queue. - assert_ne!(Queue::::get(STASH), None); + assert_ne!(Queue::::get(1), None); }); } @@ -70,13 +55,13 @@ fn register_works() { fn cannot_register_if_not_bonded() { ExtBuilder::default().build_and_execute(|| { // Mint accounts 1 and 2 with 200 tokens. - for i in 1..2 { + for _ in 1..2 { let _ = Balances::make_free_balance_be(&1, 200); } // Attempt to fast unstake. assert_noop!( FastUnstake::register_fast_unstake(Origin::signed(1), Some(1_u32)), - Error::::NotController + Error::::NotController ); }); } @@ -85,11 +70,11 @@ fn cannot_register_if_not_bonded() { fn cannot_register_if_in_queue() { ExtBuilder::default().build_and_execute(|| { // Insert some Queue item - Queue::::insert(STASH, Some(1_u32)); + Queue::::insert(1, Some(1_u32)); // Cannot re-register, already in queue assert_noop!( - FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)), - Error::::AlreadyQueued + FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32)), + Error::::AlreadyQueued ); }); } @@ -98,15 +83,11 @@ fn cannot_register_if_in_queue() { fn cannot_register_if_head() { ExtBuilder::default().build_and_execute(|| { // Insert some Head item for stash - Head::::put(UnstakeRequest { - stash: STASH.clone(), - checked: vec![], - maybe_pool_id: None, - }); + Head::::put(UnstakeRequest { stash: 1.clone(), checked: vec![], maybe_pool_id: None }); // Controller attempts to regsiter assert_noop!( - FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)), - Error::::AlreadyHead + FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32)), + Error::::AlreadyHead ); }); } @@ -115,11 +96,11 @@ fn cannot_register_if_head() { fn cannot_register_if_has_unlocking_chunks() { ExtBuilder::default().build_and_execute(|| { // Start unbonding half of staked tokens - assert_ok!(Staking::unbond(Origin::signed(CONTROLLER), 50_u128)); + assert_ok!(Staking::unbond(Origin::signed(2), 50_u128)); // Cannot register for fast unstake with unlock chunks active assert_noop!( - FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32)), - Error::::NotFullyBonded + FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32)), + Error::::NotFullyBonded ); }); } @@ -128,11 +109,11 @@ fn cannot_register_if_has_unlocking_chunks() { fn deregister_works() { ExtBuilder::default().build_and_execute(|| { // Controller account registers for fast unstake. - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); // Controller then changes mind and deregisters. - assert_ok!(FastUnstake::deregister(Origin::signed(CONTROLLER))); + assert_ok!(FastUnstake::deregister(Origin::signed(2))); // Ensure stash no longer exists in the queue. - assert_eq!(Queue::::get(STASH), None); + assert_eq!(Queue::::get(1), None); }); } @@ -140,12 +121,9 @@ fn deregister_works() { fn cannot_deregister_if_not_controller() { ExtBuilder::default().build_and_execute(|| { // Controller account registers for fast unstake. - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); // Stash tries to deregister. - assert_noop!( - FastUnstake::deregister(Origin::signed(STASH)), - Error::::NotController - ); + assert_noop!(FastUnstake::deregister(Origin::signed(1)), Error::::NotController); }); } @@ -153,29 +131,19 @@ fn cannot_deregister_if_not_controller() { fn cannot_deregister_if_not_queued() { ExtBuilder::default().build_and_execute(|| { // Controller tries to deregister without first registering - assert_noop!( - FastUnstake::deregister(Origin::signed(CONTROLLER)), - Error::::NotQueued - ); + assert_noop!(FastUnstake::deregister(Origin::signed(2)), Error::::NotQueued); }); } #[test] fn cannot_deregister_already_head() { ExtBuilder::default().build_and_execute(|| { - // Controller attempts to regsiter, should fail - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); + // Controller attempts to register, should fail + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); // Insert some Head item for stash. - Head::::put(UnstakeRequest { - stash: STASH.clone(), - checked: vec![], - maybe_pool_id: None, - }); + Head::::put(UnstakeRequest { stash: 1.clone(), checked: vec![], maybe_pool_id: None }); // Controller attempts to deregister - assert_noop!( - FastUnstake::deregister(Origin::signed(CONTROLLER)), - Error::::AlreadyHead - ); + assert_noop!(FastUnstake::deregister(Origin::signed(2)), Error::::AlreadyHead); }); } @@ -201,71 +169,221 @@ mod on_idle { #[test] fn early_exit() { ExtBuilder::default().build_and_execute(|| { - ErasToCheckPerBlock::::put(BondingDuration::get() + 1); - CurrentEra::::put(BondingDuration::get()); + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); // set up Queue item - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1))); - assert_eq!(Queue::::get(STASH), Some(Some(1))); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1))); + assert_eq!(Queue::::get(1), Some(Some(1))); // call on_idle with no remaining weight - FastUnstake::on_idle(System::block_number(), 0); + FastUnstake::on_idle(System::block_number(), Weight::from_ref_time(0)); // assert nothing changed in Queue and Head - assert_eq!(Head::::get(), None); - assert_eq!(Queue::::get(STASH), Some(Some(1))); + assert_eq!(Head::::get(), None); + assert_eq!(Queue::::get(1), Some(Some(1))); }); } #[test] fn respects_weight() { - // TODO: KIAN - todo!( - "ErasToCheckPerBlock is 5, but the remaining weight is such that we can only process 2" - ); + ExtBuilder::default().build_and_execute(|| { + // we want to check all eras in one block... + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // given + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1))); + assert_eq!(Queue::::get(1), Some(Some(1))); + + assert_eq!(Queue::::count(), 1); + assert_eq!(Head::::get(), None); + + // when: call fast unstake with not enough weight to process the whole thing, just one + // era. + let remaining_weight = ::WeightInfo::on_idle_check( + pallet_staking::ValidatorCount::::get() * 1, + ); + assert_eq!(FastUnstake::on_idle(0, remaining_weight), remaining_weight); + + // then + assert_eq!( + fast_unstake_events_since_last_call(), + vec![Event::Checked { stash: 1, eras: vec![3] }] + ); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![3], maybe_pool_id: Some(1) }) + ); + + // when: another 1 era. + let remaining_weight = ::WeightInfo::on_idle_check( + pallet_staking::ValidatorCount::::get() * 1, + ); + assert_eq!(FastUnstake::on_idle(0, remaining_weight), remaining_weight); + + // then: + assert_eq!( + fast_unstake_events_since_last_call(), + vec![Event::Checked { stash: 1, eras: vec![2] }] + ); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![3, 2], maybe_pool_id: Some(1) }) + ); + + // when: then 5 eras, we only need 2 more. + let remaining_weight = ::WeightInfo::on_idle_check( + pallet_staking::ValidatorCount::::get() * 5, + ); + assert_eq!( + FastUnstake::on_idle(0, remaining_weight), + // note the amount of weight consumed: 2 eras worth of weight. + ::WeightInfo::on_idle_check( + pallet_staking::ValidatorCount::::get() * 2, + ) + ); + + // then: + assert_eq!( + fast_unstake_events_since_last_call(), + vec![Event::Checked { stash: 1, eras: vec![1, 0] }] + ); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stash: 1, + checked: vec![3, 2, 1, 0], + maybe_pool_id: Some(1) + }) + ); + + // when: not enough weight to unstake: + let remaining_weight = + ::WeightInfo::on_idle_unstake() - Weight::from_ref_time(1); + assert_eq!(FastUnstake::on_idle(0, remaining_weight), Weight::from_ref_time(0)); + + // then nothing happens: + assert_eq!(fast_unstake_events_since_last_call(), vec![]); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stash: 1, + checked: vec![3, 2, 1, 0], + maybe_pool_id: Some(1) + }) + ); + + // when: enough weight to get over at least one iteration: then we are unblocked and can + // unstake. + let remaining_weight = ::WeightInfo::on_idle_check( + pallet_staking::ValidatorCount::::get() * 1, + ); + assert_eq!( + FastUnstake::on_idle(0, remaining_weight), + ::WeightInfo::on_idle_unstake() + ); + + // then we finish the unbonding: + assert_eq!( + fast_unstake_events_since_last_call(), + vec![Event::Unstaked { stash: 1, maybe_pool_id: Some(1), result: Ok(()) }] + ); + assert_eq!(Head::::get(), None,); + + assert_unstaked(&1); + }); } #[test] - fn successful_multi_queue() { + fn if_head_not_set_one_random_fetched_from_queue() { ExtBuilder::default().build_and_execute(|| { - let max_block_weight = BlockWeights::get().max_block; + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // given + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), None)); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(4), None)); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(6), None)); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(8), None)); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(10), None)); + + assert_eq!(Queue::::count(), 5); + assert_eq!(Head::::get(), None); - ErasToCheckPerBlock::::put(BondingDuration::get() + 1); - CurrentEra::::put(BondingDuration::get()); + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![3, 2, 1, 0], maybe_pool_id: None }) + ); + assert_eq!(Queue::::count(), 4); + + // when + next_block(true); + + // then + assert_eq!(Head::::get(), None,); + assert_eq!(Queue::::count(), 4); + + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 5, checked: vec![3, 2, 1, 0], maybe_pool_id: None }), + ); + assert_eq!(Queue::::count(), 3); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::Checked { stash: 1, eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 1, maybe_pool_id: None, result: Ok(()) }, + Event::Checked { stash: 5, eras: vec![3, 2, 1, 0] } + ] + ); + }); + } + + #[test] + fn successful_multi_queue() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); // register multi accounts for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1))); - assert_eq!(Queue::::get(STASH), Some(Some(1))); - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER_2), Some(1))); - assert_eq!(Queue::::get(STASH_2), Some(Some(1))); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1))); + assert_eq!(Queue::::get(1), Some(Some(1))); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(4), Some(1))); + assert_eq!(Queue::::get(3), Some(Some(1))); // assert 2 queue items are in Queue & None in Head to start with - assert_eq!(Queue::::count(), 2); - assert_eq!(Head::::get(), None); + assert_eq!(Queue::::count(), 2); + assert_eq!(Head::::get(), None); // process on idle and check eras for next Queue item - FastUnstake::on_idle(System::block_number(), max_block_weight); - run_to_block(System::block_number() + 1); + next_block(true); // process on idle & let go of current Head - FastUnstake::on_idle(System::block_number(), max_block_weight); - run_to_block(System::block_number() + 1); + next_block(true); // confirm Head / Queue items remaining - assert_eq!(Head::::get(), None); - assert_eq!(Queue::::count(), 1); + assert_eq!(Head::::get(), None); + assert_eq!(Queue::::count(), 1); // process on idle and check eras for next Queue item - FastUnstake::on_idle(System::block_number(), max_block_weight); - run_to_block(System::block_number() + 1); + next_block(true); // process on idle & let go of current Head - FastUnstake::on_idle(System::block_number(), max_block_weight); - run_to_block(System::block_number() + 1); + next_block(true); // Head & Queue should now be empty - assert_eq!(Head::::get(), None); - assert_eq!(Queue::::count(), 0); + assert_eq!(Head::::get(), None); + assert_eq!(Queue::::count(), 0); assert_eq!( fast_unstake_events_since_last_call(), @@ -276,38 +394,36 @@ mod on_idle { Event::Unstaked { stash: 3, maybe_pool_id: Some(1), result: Ok(()) }, ] ); - assert_unstaked(&STASH); - assert_unstaked(&STASH_2); + + assert_unstaked(&1); + assert_unstaked(&3); }); } #[test] fn successful_unstake_without_pool_join() { ExtBuilder::default().build_and_execute(|| { - ErasToCheckPerBlock::::put(BondingDuration::get() + 1); - CurrentEra::::put(BondingDuration::get()); + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), None)); - assert_eq!(Queue::::get(STASH), Some(None)); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), None)); + assert_eq!(Queue::::get(1), Some(None)); // process on idle - let remaining_weight = BlockWeights::get().max_block; - FastUnstake::on_idle(Zero::zero(), remaining_weight); + next_block(true); // assert queue item has been moved to head - assert_eq!(Queue::::get(STASH), None); + assert_eq!(Queue::::get(1), None); // assert head item present assert_eq!( - Head::::get(), + Head::::get(), Some(UnstakeRequest { stash: 1, checked: vec![3, 2, 1, 0], maybe_pool_id: None }) ); - let remaining_weight = BlockWeights::get().max_block; - FastUnstake::on_idle(Zero::zero(), remaining_weight); - - assert_eq!(Head::::get(), None,); + next_block(true); + assert_eq!(Head::::get(), None,); assert_eq!( fast_unstake_events_since_last_call(), @@ -316,30 +432,29 @@ mod on_idle { Event::Unstaked { stash: 1, maybe_pool_id: None, result: Ok(()) } ] ); - assert_unstaked(&STASH); + assert_unstaked(&1); }); } #[test] fn successful_unstake_joining_bad_pool() { ExtBuilder::default().build_and_execute(|| { - ErasToCheckPerBlock::::put(BondingDuration::get() + 1); - CurrentEra::::put(BondingDuration::get()); + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(0))); - assert_eq!(Queue::::get(STASH), Some(Some(0))); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(0))); + assert_eq!(Queue::::get(1), Some(Some(0))); // process on idle - let remaining_weight = BlockWeights::get().max_block; - FastUnstake::on_idle(Zero::zero(), remaining_weight); + next_block(true); // assert queue item has been moved to head - assert_eq!(Queue::::get(STASH), None); + assert_eq!(Queue::::get(1), None); // assert head item present assert_eq!( - Head::::get(), + Head::::get(), Some(UnstakeRequest { stash: 1, checked: vec![3, 2, 1, 0], @@ -347,10 +462,8 @@ mod on_idle { }) ); - let remaining_weight = BlockWeights::get().max_block; - FastUnstake::on_idle(Zero::zero(), remaining_weight); - - assert_eq!(Head::::get(), None,); + next_block(true); + assert_eq!(Head::::get(), None,); assert_eq!( fast_unstake_events_since_last_call(), @@ -367,30 +480,29 @@ mod on_idle { } ] ); - assert_unstaked(&STASH); + assert_unstaked(&1); }); } #[test] fn successful_unstake_all_eras_per_block() { ExtBuilder::default().build_and_execute(|| { - ErasToCheckPerBlock::::put(BondingDuration::get() + 1); - CurrentEra::::put(BondingDuration::get()); + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); - assert_eq!(Queue::::get(STASH), Some(Some(1))); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); + assert_eq!(Queue::::get(1), Some(Some(1))); // process on idle - let remaining_weight = BlockWeights::get().max_block; - FastUnstake::on_idle(Zero::zero(), remaining_weight); + next_block(true); // assert queue item has been moved to head - assert_eq!(Queue::::get(STASH), None); + assert_eq!(Queue::::get(1), None); // assert head item present assert_eq!( - Head::::get(), + Head::::get(), Some(UnstakeRequest { stash: 1, checked: vec![3, 2, 1, 0], @@ -398,10 +510,8 @@ mod on_idle { }) ); - let remaining_weight = BlockWeights::get().max_block; - FastUnstake::on_idle(Zero::zero(), remaining_weight); - - assert_eq!(Head::::get(), None,); + next_block(true); + assert_eq!(Head::::get(), None,); assert_eq!( fast_unstake_events_since_last_call(), @@ -410,8 +520,8 @@ mod on_idle { Event::Unstaked { stash: 1, maybe_pool_id: Some(1), result: Ok(()) } ] ); - assert_unstaked(&STASH); - assert!(pallet_nomination_pools::PoolMembers::::contains_key(&STASH)); + assert_unstaked(&1); + assert!(pallet_nomination_pools::PoolMembers::::contains_key(&1)); }); } @@ -419,47 +529,43 @@ mod on_idle { fn successful_unstake_one_era_per_block() { ExtBuilder::default().build_and_execute(|| { // put 1 era per block - ErasToCheckPerBlock::::put(1); - CurrentEra::::put(BondingDuration::get()); + ErasToCheckPerBlock::::put(1); + CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); - assert_eq!(Queue::::get(STASH), Some(Some(1))); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); + assert_eq!(Queue::::get(1), Some(Some(1))); // process on idle - let remaining_weight = BlockWeights::get().max_block; - FastUnstake::on_idle(Zero::zero(), remaining_weight); + next_block(true); // assert queue item has been moved to head - assert_eq!(Queue::::get(STASH), None); + assert_eq!(Queue::::get(1), None); // assert head item present assert_eq!( - Head::::get(), + Head::::get(), Some(UnstakeRequest { stash: 1, checked: vec![3], maybe_pool_id: Some(1) }) ); - let remaining_weight = BlockWeights::get().max_block; - FastUnstake::on_idle(Zero::zero(), remaining_weight); + next_block(true); assert_eq!( - Head::::get(), + Head::::get(), Some(UnstakeRequest { stash: 1, checked: vec![3, 2], maybe_pool_id: Some(1) }) ); - let remaining_weight = BlockWeights::get().max_block; - FastUnstake::on_idle(Zero::zero(), remaining_weight); + next_block(true); assert_eq!( - Head::::get(), + Head::::get(), Some(UnstakeRequest { stash: 1, checked: vec![3, 2, 1], maybe_pool_id: Some(1) }) ); - let remaining_weight = BlockWeights::get().max_block; - FastUnstake::on_idle(Zero::zero(), remaining_weight); + next_block(true); assert_eq!( - Head::::get(), + Head::::get(), Some(UnstakeRequest { stash: 1, checked: vec![3, 2, 1, 0], @@ -467,10 +573,9 @@ mod on_idle { }) ); - let remaining_weight = BlockWeights::get().max_block; - FastUnstake::on_idle(Zero::zero(), remaining_weight); + next_block(true); - assert_eq!(Head::::get(), None,); + assert_eq!(Head::::get(), None,); assert_eq!( fast_unstake_events_since_last_call(), @@ -482,23 +587,94 @@ mod on_idle { Event::Unstaked { stash: 1, maybe_pool_id: Some(1), result: Ok(()) } ] ); - assert_unstaked(&STASH); - assert!(pallet_nomination_pools::PoolMembers::::contains_key(&STASH)); + assert_unstaked(&1); + assert!(pallet_nomination_pools::PoolMembers::::contains_key(&1)); }); } #[test] fn unstake_paused_mid_election() { - // TODO: KIAN ExtBuilder::default().build_and_execute(|| { - // Initiate staking position - // Controller account registers for fast unstake. - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(CONTROLLER), Some(1_u32))); - // todo!( - // "a dude is being unstaked, midway being checked, election happens, they are still not - // exposed, but a new era needs to be checked, therefore this unstake takes longer than - // expected." - // ) + // give: put 1 era per block + ErasToCheckPerBlock::::put(1); + CurrentEra::::put(BondingDuration::get()); + + // register for fast unstake + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); + + // process 2 blocks + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![3], maybe_pool_id: Some(1) }) + ); + + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![3, 2], maybe_pool_id: Some(1) }) + ); + + // when + Ongoing::set(true); + + // then nothing changes + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![3, 2], maybe_pool_id: Some(1) }) + ); + + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![3, 2], maybe_pool_id: Some(1) }) + ); + + // then we register a new era. + Ongoing::set(false); + CurrentEra::::put(CurrentEra::::get().unwrap() + 1); + ExtBuilder::register_stakers_for_era( + CurrentEra::::get().unwrap(), + VALIDATORS_PER_ERA, + NOMINATORS_PER_VALIDATOR_PER_ERA, + ); + + // then we can progress again, but notice that the new era that had to be checked. + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![3, 2, 4], maybe_pool_id: Some(1) }) + ); + + // progress to end + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stash: 1, + checked: vec![3, 2, 4, 1], + maybe_pool_id: Some(1) + }) + ); + + // but notice that we don't care about era 0 instead anymore! we're done. + next_block(true); + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::Checked { stash: 1, eras: vec![3] }, + Event::Checked { stash: 1, eras: vec![2] }, + Event::Checked { stash: 1, eras: vec![4] }, + Event::Checked { stash: 1, eras: vec![1] }, + Event::Unstaked { stash: 1, maybe_pool_id: Some(1), result: Ok(()) } + ] + ); + + assert_unstaked(&1); + assert!(pallet_nomination_pools::PoolMembers::::contains_key(&1)); }); } } diff --git a/frame/fast-unstake/src/weights.rs b/frame/fast-unstake/src/weights.rs new file mode 100644 index 0000000000000..04857d0dcc865 --- /dev/null +++ b/frame/fast-unstake/src/weights.rs @@ -0,0 +1,210 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_fast_unstake +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-09-07, STEPS: `10`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `Kians-MacBook-Pro-2.local`, CPU: `` +//! EXECUTION: Some(Native), WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 + +// Executed Command: +// target/release/substrate +// benchmark +// pallet +// --steps=10 +// --repeat=1 +// --pallet=pallet_fast_unstake +// --extrinsic=* +// --execution=native +// --output +// weight.rs +// --template +// ./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_fast_unstake. +pub trait WeightInfo { + fn on_idle_unstake() -> Weight; + fn on_idle_check(x: u32, ) -> Weight; + fn register_fast_unstake() -> Weight; + fn deregister() -> Weight; + fn control() -> Weight; +} + +/// Weights for pallet_fast_unstake using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + // Storage: FastUnstake Head (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Bonded (r:2 w:1) + // Storage: Staking Ledger (r:2 w:2) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: System Account (r:3 w:2) + // Storage: Balances Locks (r:2 w:2) + // Storage: NominationPools MinJoinBond (r:1 w:0) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + // Storage: NominationPools MaxPoolMembers (r:1 w:0) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: BagsList ListNodes (r:1 w:0) + // Storage: Staking Payee (r:0 w:1) + fn on_idle_unstake() -> Weight { + Weight::from_ref_time(102_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(25 as u64)) + .saturating_add(T::DbWeight::get().writes(13 as u64)) + } + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + // Storage: FastUnstake Head (r:1 w:1) + // Storage: FastUnstake Queue (r:2 w:1) + // Storage: FastUnstake CounterForQueue (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking ErasStakers (r:1344 w:0) + /// The range of component `x` is `[672, 86016]`. + fn on_idle_check(x: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 244_000 + .saturating_add(Weight::from_ref_time(13_913_000 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(585 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Nominators (r:1 w:1) + // Storage: FastUnstake Queue (r:1 w:1) + // Storage: FastUnstake Head (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: BagsList ListNodes (r:1 w:1) + // Storage: BagsList ListBags (r:1 w:1) + // Storage: BagsList CounterForListNodes (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: FastUnstake CounterForQueue (r:1 w:1) + fn register_fast_unstake() -> Weight { + Weight::from_ref_time(57_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(12 as u64)) + .saturating_add(T::DbWeight::get().writes(9 as u64)) + } + // Storage: Staking Ledger (r:1 w:0) + // Storage: FastUnstake Queue (r:1 w:1) + // Storage: FastUnstake Head (r:1 w:0) + // Storage: FastUnstake CounterForQueue (r:1 w:1) + fn deregister() -> Weight { + Weight::from_ref_time(15_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: FastUnstake ErasToCheckPerBlock (r:0 w:1) + fn control() -> Weight { + Weight::from_ref_time(3_000_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + // Storage: FastUnstake Head (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Bonded (r:2 w:1) + // Storage: Staking Ledger (r:2 w:2) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: System Account (r:3 w:2) + // Storage: Balances Locks (r:2 w:2) + // Storage: NominationPools MinJoinBond (r:1 w:0) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + // Storage: NominationPools MaxPoolMembers (r:1 w:0) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: BagsList ListNodes (r:1 w:0) + // Storage: Staking Payee (r:0 w:1) + fn on_idle_unstake() -> Weight { + Weight::from_ref_time(102_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(25 as u64)) + .saturating_add(RocksDbWeight::get().writes(13 as u64)) + } + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + // Storage: FastUnstake Head (r:1 w:1) + // Storage: FastUnstake Queue (r:2 w:1) + // Storage: FastUnstake CounterForQueue (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking ErasStakers (r:1344 w:0) + /// The range of component `x` is `[672, 86016]`. + fn on_idle_check(x: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 244_000 + .saturating_add(Weight::from_ref_time(13_913_000 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(585 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Nominators (r:1 w:1) + // Storage: FastUnstake Queue (r:1 w:1) + // Storage: FastUnstake Head (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: BagsList ListNodes (r:1 w:1) + // Storage: BagsList ListBags (r:1 w:1) + // Storage: BagsList CounterForListNodes (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: FastUnstake CounterForQueue (r:1 w:1) + fn register_fast_unstake() -> Weight { + Weight::from_ref_time(57_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(12 as u64)) + .saturating_add(RocksDbWeight::get().writes(9 as u64)) + } + // Storage: Staking Ledger (r:1 w:0) + // Storage: FastUnstake Queue (r:1 w:1) + // Storage: FastUnstake Head (r:1 w:0) + // Storage: FastUnstake CounterForQueue (r:1 w:1) + fn deregister() -> Weight { + Weight::from_ref_time(15_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: FastUnstake ErasToCheckPerBlock (r:0 w:1) + fn control() -> Weight { + Weight::from_ref_time(3_000_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} From ce814941e22d23f1dcb1fddf6c641962278c8b74 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 8 Sep 2022 10:31:15 +0000 Subject: [PATCH 48/81] seemingly it is finished now --- frame/fast-unstake/src/lib.rs | 12 +++++- frame/fast-unstake/src/tests.rs | 69 ++++++++++++++++++++++++++++++--- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 18b116aa01c55..07bc7823ed7e0 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -307,8 +307,7 @@ pub mod pallet { .max(::WeightInfo::on_idle_unstake()) }; let mut try_eras_to_check = eras_to_check_per_block; - while dbg!(worse_weight(validator_count, try_eras_to_check)) > - dbg!(remaining_weight) + while worse_weight(validator_count, try_eras_to_check) > remaining_weight { try_eras_to_check.saturating_dec(); if try_eras_to_check.is_zero() { @@ -484,6 +483,13 @@ pub mod pallet { sp_std::marker::PhantomData, ); + #[cfg(test)] + impl PreventStakingOpsIfUnbonding { + pub fn new() -> Self { + Self(Default::default()) + } + } + impl sp_runtime::traits::SignedExtension for PreventStakingOpsIfUnbonding where @@ -520,7 +526,9 @@ pub mod pallet { } }; match ( + // mapped from controller. pallet_staking::Ledger::::get(&stash_or_controller), + // mapped from stash. pallet_staking::Bonded::::get(&stash_or_controller), ) { (Some(ledger), None) => { diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 8ee2b1abf03ed..de9fcef5d0521 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -23,10 +23,7 @@ use frame_support::{assert_noop, assert_ok, pallet_prelude::*, traits::Currency} use pallet_nomination_pools::{BondedPools, LastPoolId, RewardPools}; use pallet_staking::CurrentEra; -use sp_runtime::{ - traits::BadOrigin, - DispatchError, ModuleError, -}; +use sp_runtime::{traits::BadOrigin, DispatchError, ModuleError}; use sp_staking::StakingInterface; use sp_std::prelude::*; @@ -681,5 +678,67 @@ mod on_idle { mod signed_extension { use super::*; - // TODO: + use crate::PreventStakingOpsIfUnbonding; + use sp_runtime::traits::SignedExtension; + + const STAKING_CALL: crate::mock::Call = + crate::mock::Call::Staking(pallet_staking::Call::::chill {}); + + #[test] + fn does_nothing_if_not_queued() { + ExtBuilder::default().build_and_execute(|| { + assert!(PreventStakingOpsIfUnbonding::::new() + .pre_dispatch(&1, &STAKING_CALL, &Default::default(), Default::default()) + .is_ok()); + }) + } + + #[test] + fn prevents_queued() { + ExtBuilder::default().build_and_execute(|| { + // given: stash for 2 is 1. + // when + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), None)); + + // then + // stash can't. + assert!(PreventStakingOpsIfUnbonding::::new() + .pre_dispatch(&1, &STAKING_CALL, &Default::default(), Default::default()) + .is_err()); + + // controller can't. + assert!(PreventStakingOpsIfUnbonding::::new() + .pre_dispatch(&2, &STAKING_CALL, &Default::default(), Default::default()) + .is_err()); + }) + } + + #[test] + fn prevents_head_stash() { + ExtBuilder::default().build_and_execute(|| { + // given: stash for 2 is 1. + // when + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), None)); + + ErasToCheckPerBlock::::put(1); + CurrentEra::::put(BondingDuration::get()); + next_block(true); + + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: vec![3], maybe_pool_id: None }) + ); + + // then + // stash can't + assert!(PreventStakingOpsIfUnbonding::::new() + .pre_dispatch(&2, &STAKING_CALL, &Default::default(), Default::default()) + .is_err()); + + // controller can't + assert!(PreventStakingOpsIfUnbonding::::new() + .pre_dispatch(&1, &STAKING_CALL, &Default::default(), Default::default()) + .is_err()); + }) + } } From fe1915759584343c13df77c709823f8184972410 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 8 Sep 2022 10:38:23 +0000 Subject: [PATCH 49/81] Fix build --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 07bc7823ed7e0..8f6bca0837c17 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -307,7 +307,7 @@ pub mod pallet { .max(::WeightInfo::on_idle_unstake()) }; let mut try_eras_to_check = eras_to_check_per_block; - while worse_weight(validator_count, try_eras_to_check) > remaining_weight + while worse_weight(validator_count, try_eras_to_check).all_gt(remaining_weight) { try_eras_to_check.saturating_dec(); if try_eras_to_check.is_zero() { From 61d041b050678a1ded33cb2013a6fdde210a2b8a Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Thu, 8 Sep 2022 11:03:54 +0000 Subject: [PATCH 50/81] ".git/.scripts/fmt.sh" 1 --- frame/fast-unstake/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 8f6bca0837c17..ff9471bfb8002 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -307,8 +307,7 @@ pub mod pallet { .max(::WeightInfo::on_idle_unstake()) }; let mut try_eras_to_check = eras_to_check_per_block; - while worse_weight(validator_count, try_eras_to_check).all_gt(remaining_weight) - { + while worse_weight(validator_count, try_eras_to_check).all_gt(remaining_weight) { try_eras_to_check.saturating_dec(); if try_eras_to_check.is_zero() { log!(debug, "early existing because try_eras_to_check is zero"); From c3e292cdc9af5dd26538c91e9f850344db86a4ac Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 9 Sep 2022 08:46:38 +0000 Subject: [PATCH 51/81] Fix slashing amount as well --- frame/fast-unstake/src/lib.rs | 26 +++-- frame/fast-unstake/src/mock.rs | 50 ++++++---- frame/fast-unstake/src/tests.rs | 164 ++++++++++++++++++++++++++++++-- frame/staking/src/pallet/mod.rs | 4 +- 4 files changed, 206 insertions(+), 38 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 8f6bca0837c17..f6cf69b808f3f 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -172,16 +172,16 @@ pub mod pallet { #[cfg_attr(test, derive(PartialEq))] pub enum Error { /// The provided Controller account was not found. + /// + /// This means that the given account is not bonded. NotController, - /// The nominator was not found. - NotNominator, /// The bonded account has already been queued. AlreadyQueued, /// The bonded account has active unlocking chunks. NotFullyBonded, - /// The provided unstaker is not in the `Queue`. + /// The provided un-staker is not in the `Queue`. NotQueued, - /// The provided unstaker is already in Head, and cannot deregister. + /// The provided un-staker is already in Head, and cannot deregister. AlreadyHead, } @@ -221,10 +221,6 @@ pub mod pallet { let ledger = pallet_staking::Ledger::::get(&ctrl).ok_or(Error::::NotController)?; - ensure!( - pallet_staking::Nominators::::contains_key(&ledger.stash), - Error::::NotNominator - ); ensure!(!Queue::::contains_key(&ledger.stash), Error::::AlreadyQueued); ensure!( Head::::get().map_or(true, |UnstakeRequest { stash, .. }| stash != ledger.stash), @@ -307,8 +303,7 @@ pub mod pallet { .max(::WeightInfo::on_idle_unstake()) }; let mut try_eras_to_check = eras_to_check_per_block; - while worse_weight(validator_count, try_eras_to_check).all_gt(remaining_weight) - { + while worse_weight(validator_count, try_eras_to_check).all_gt(remaining_weight) { try_eras_to_check.saturating_dec(); if try_eras_to_check.is_zero() { log!(debug, "early existing because try_eras_to_check is zero"); @@ -439,8 +434,9 @@ pub mod pallet { log!( debug, - "checked {:?} eras, (v: {:?}, u: {:?})", + "checked {:?} eras, exposed? {}, (v: {:?}, u: {:?})", eras_checked, + is_exposed, validator_count, eras_to_check.len() ); @@ -449,7 +445,8 @@ pub mod pallet { // the last 28 eras, have registered yourself to be unstaked, midway being checked, // you are exposed. if is_exposed { - let amount = T::SlashPerEra::get().saturating_mul(eras_checked.into()); + let amount = T::SlashPerEra::get() + .saturating_mul(eras_checked.saturating_add(checked.len() as u32).into()); pallet_staking::slashing::do_slash::( &stash, amount, @@ -472,8 +469,9 @@ pub mod pallet { /// Checks whether an account `who` has been exposed in an era. fn is_exposed_in_era(who: &T::AccountId, era: &EraIndex) -> bool { - pallet_staking::ErasStakers::::iter_prefix(era) - .any(|(_, exposures)| exposures.others.iter().any(|i| i.who == *who)) + pallet_staking::ErasStakers::::iter_prefix(era).any(|(validator, exposures)| { + validator == *who || exposures.others.iter().any(|i| i.who == *who) + }) } } diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index b81b69d7f2da0..cb5a9431fed79 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -30,7 +30,7 @@ use sp_runtime::{ }; use frame_system::RawOrigin; -use pallet_staking::{Exposure, IndividualExposure}; +use pallet_staking::{Exposure, IndividualExposure, StakerStatus}; use sp_std::prelude::*; pub type AccountId = u128; @@ -237,28 +237,40 @@ pub(crate) fn fast_unstake_events_since_last_call() -> Vec } pub struct ExtBuilder { - stakers: Vec<(AccountId, AccountId, Balance)>, + exposed_nominators: Vec<(AccountId, AccountId, Balance)>, } impl Default for ExtBuilder { fn default() -> Self { - Self { stakers: vec![(1, 2, 100), (3, 4, 100), (5, 6, 100), (7, 8, 100), (9, 10, 100)] } + Self { + exposed_nominators: vec![ + (1, 2, 100), + (3, 4, 100), + (5, 6, 100), + (7, 8, 100), + (9, 10, 100), + ], + } } } pub(crate) const VALIDATORS_PER_ERA: AccountId = 32; +pub(crate) const VALIDATOR_PREFIX: AccountId = 100; pub(crate) const NOMINATORS_PER_VALIDATOR_PER_ERA: AccountId = 4; +pub(crate) const NOMINATOR_PREFIX: AccountId = 1000; impl ExtBuilder { - pub(crate) fn register_stakers_for_era(era: u32, validators: AccountId, nominators: AccountId) { + pub(crate) fn register_stakers_for_era(era: u32) { // validators are prefixed with 100 and nominators with 1000 to prevent conflict. Make sure // all the other accounts used in tests are below 100. Also ensure here that we don't // overlap. - assert!(100 + validators < 10000); + assert!(VALIDATOR_PREFIX + VALIDATORS_PER_ERA < NOMINATOR_PREFIX); - (100..100 + validators) + (VALIDATOR_PREFIX..VALIDATOR_PREFIX + VALIDATORS_PER_ERA) .map(|v| { - let others = (1000..(1000 + nominators)) + // for the sake of sanity, let's register this taker as an actual validator. + let others = (NOMINATOR_PREFIX.. + (NOMINATOR_PREFIX + NOMINATORS_PER_VALIDATOR_PER_ERA)) .map(|n| IndividualExposure { who: n, value: 0 as Balance }) .collect::>(); (v, Exposure { total: 0, own: 0, others }) @@ -277,24 +289,35 @@ impl ExtBuilder { let _ = pallet_nomination_pools::GenesisConfig:: { ..Default::default() } .assimilate_storage(&mut storage); + let validators_range = VALIDATOR_PREFIX..VALIDATOR_PREFIX + VALIDATORS_PER_ERA; + let nominators_range = + NOMINATOR_PREFIX..NOMINATOR_PREFIX + NOMINATORS_PER_VALIDATOR_PER_ERA; + let _ = pallet_balances::GenesisConfig:: { balances: self - .stakers + .exposed_nominators .clone() .into_iter() .map(|(stash, _, balance)| (stash, balance * 2)) .chain( - self.stakers.clone().into_iter().map(|(_, ctrl, balance)| (ctrl, balance * 2)), + self.exposed_nominators + .clone() + .into_iter() + .map(|(_, ctrl, balance)| (ctrl, balance * 2)), ) + .chain(validators_range.clone().map(|x| (x, 100))) + .chain(nominators_range.clone().map(|x| (x, 100))) .collect::>(), } .assimilate_storage(&mut storage); let _ = pallet_staking::GenesisConfig:: { stakers: self - .stakers + .exposed_nominators .into_iter() .map(|(x, y, z)| (x, y, z, pallet_staking::StakerStatus::Nominator(vec![42]))) + .chain(validators_range.map(|x| (x, x, 100, StakerStatus::Validator))) + .chain(nominators_range.map(|x| (x, x, 100, StakerStatus::Nominator(vec![x])))) .collect::>(), ..Default::default() } @@ -307,11 +330,7 @@ impl ExtBuilder { frame_system::Pallet::::set_block_number(1); for era in 0..=(BondingDuration::get()) { - Self::register_stakers_for_era( - era, - VALIDATORS_PER_ERA, - NOMINATORS_PER_VALIDATOR_PER_ERA, - ); + Self::register_stakers_for_era(era); } // because we read this value as a measure of how many validators we have. @@ -328,7 +347,6 @@ impl ExtBuilder { pub fn build_and_execute(self, test: impl FnOnce() -> ()) { self.build().execute_with(|| { test(); - // TODO: sanity check, if any }) } } diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index de9fcef5d0521..8f1e05a9928dc 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -21,7 +21,7 @@ use super::*; use crate::{mock::*, weights::WeightInfo, Event}; use frame_support::{assert_noop, assert_ok, pallet_prelude::*, traits::Currency}; use pallet_nomination_pools::{BondedPools, LastPoolId, RewardPools}; -use pallet_staking::CurrentEra; +use pallet_staking::{CurrentEra, IndividualExposure, RewardDestination}; use sp_runtime::{traits::BadOrigin, DispatchError, ModuleError}; use sp_staking::StakingInterface; @@ -631,11 +631,7 @@ mod on_idle { // then we register a new era. Ongoing::set(false); CurrentEra::::put(CurrentEra::::get().unwrap() + 1); - ExtBuilder::register_stakers_for_era( - CurrentEra::::get().unwrap(), - VALIDATORS_PER_ERA, - NOMINATORS_PER_VALIDATOR_PER_ERA, - ); + ExtBuilder::register_stakers_for_era(CurrentEra::::get().unwrap()); // then we can progress again, but notice that the new era that had to be checked. next_block(true); @@ -674,6 +670,162 @@ mod on_idle { assert!(pallet_nomination_pools::PoolMembers::::contains_key(&1)); }); } + + #[test] + fn exposed_nominator_cannot_unstake() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + SlashPerEra::set(7); + CurrentEra::::put(BondingDuration::get()); + + // create an exposed nominator in era 1 + let exposed = 666 as AccountId; + pallet_staking::ErasStakers::::mutate(1, VALIDATORS_PER_ERA, |expo| { + expo.others.push(IndividualExposure { who: exposed, value: 0 as Balance }); + }); + Balances::make_free_balance_be(&exposed, 100); + assert_ok!(Staking::bond( + Origin::signed(exposed), + exposed, + 10, + RewardDestination::Staked + )); + assert_ok!(Staking::nominate(Origin::signed(exposed), vec![exposed])); + + // register the exposed one. + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(exposed), None)); + + // a few blocks later, we realize they are slashed + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: exposed, checked: vec![3], maybe_pool_id: None }) + ); + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: exposed, checked: vec![3, 2], maybe_pool_id: None }) + ); + next_block(true); + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + // we slash them by 21, since we checked 3 eras in total (3, 2, 1). + vec![ + Event::Checked { stash: exposed, eras: vec![3] }, + Event::Checked { stash: exposed, eras: vec![2] }, + Event::Slashed { stash: exposed, amount: 3 * 7 } + ] + ); + }); + } + + #[test] + fn exposed_nominator_cannot_unstake_multi_check() { + ExtBuilder::default().build_and_execute(|| { + // same as the previous check, but we check 2 eras per block, and we make the exposed be + // exposed in era 0, so that it is detected halfway in a check era. + ErasToCheckPerBlock::::put(2); + SlashPerEra::set(7); + CurrentEra::::put(BondingDuration::get()); + + // create an exposed nominator in era 1 + let exposed = 666 as AccountId; + pallet_staking::ErasStakers::::mutate(0, VALIDATORS_PER_ERA, |expo| { + expo.others.push(IndividualExposure { who: exposed, value: 0 as Balance }); + }); + Balances::make_free_balance_be(&exposed, 100); + assert_ok!(Staking::bond( + Origin::signed(exposed), + exposed, + 10, + RewardDestination::Staked + )); + assert_ok!(Staking::nominate(Origin::signed(exposed), vec![exposed])); + + // register the exposed one. + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(exposed), None)); + + // a few blocks later, we realize they are slashed + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: exposed, checked: vec![3, 2], maybe_pool_id: None }) + ); + next_block(true); + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + // we slash them by 28, since we checked 4 eras in total. + vec![ + Event::Checked { stash: exposed, eras: vec![3, 2] }, + Event::Slashed { stash: exposed, amount: 4 * 7 } + ] + ); + }); + } + + #[test] + fn validators_cannot_bail() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // a validator switches role and register... + assert_ok!(Staking::nominate(Origin::signed(VALIDATOR_PREFIX), vec![VALIDATOR_PREFIX])); + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(VALIDATOR_PREFIX), None)); + + // but they indeed are exposed! + assert!(pallet_staking::ErasStakers::::contains_key( + BondingDuration::get() - 1, + VALIDATOR_PREFIX + )); + + // process a block, this validator is exposed and has been slashed. + next_block(true); + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![Event::Slashed { stash: 100, amount: 100 }] + ); + }); + } + + #[test] + fn unexposed_validator_can_fast_unstake() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // create a new validator that 100% not exposed. + Balances::make_free_balance_be(&42, 100); + assert_ok!(Staking::bond(Origin::signed(42), 42, 10, RewardDestination::Staked)); + assert_ok!(Staking::validate(Origin::signed(42), Default::default())); + + // let them register: + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(42), None)); + + // 2 block's enough to unstake them. + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 42, checked: vec![3, 2, 1, 0], maybe_pool_id: None }) + ); + next_block(true); + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::Checked { stash: 42, eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 42, maybe_pool_id: None, result: Ok(()) } + ] + ); + }); + } } mod signed_extension { diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 0e090cd6f0f69..c70041f85bebd 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -617,8 +617,8 @@ pub mod pallet { EraPaid(EraIndex, BalanceOf, BalanceOf), /// The nominator has been rewarded by this amount. \[stash, amount\] Rewarded(T::AccountId, BalanceOf), - /// One validator (and its nominators) has been slashed by the given amount. - /// \[validator, amount\] + /// One staker (and potentially its nominators) has been slashed by the given amount. + /// \[staker, amount\] Slashed(T::AccountId, BalanceOf), /// An old slashing report from a prior era was discarded because it could /// not be processed. \[session_index\] From c925741d271716d21f0e34b9f4f16e08707a8852 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 9 Sep 2022 08:53:36 +0000 Subject: [PATCH 52/81] better docs --- frame/fast-unstake/src/lib.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index f6cf69b808f3f..7dea0aea00427 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -17,9 +17,16 @@ //! A pallet that's designed to ONLY do: //! -//! If a nominator is not exposed at all in any `ErasStakers` (i.e. "has not backed any validators -//! in the last `BondingDuration` days"), then they can register themselves in this pallet, and move -//! quickly into a nomination pool. +//! If a nominator is not exposed at all in any `ErasStakers` (i.e. "has not actively backed any +//! validators in the last `BondingDuration` days"), then they can register themselves in this +//! pallet, unstake faster than needing to wait an entire bonding duration, and potentially move +//! into a nomination pool. +//! +//! Appearing in the exposure of a validator means being exposed equal to that validator from the +//! point of view of the staking system. This usually means earning rewards with the validator, and +//! also being at the risk of slashing with the validator. This is equivalent to the "Active +//! Nominator" role explained in the +//! [February Staking Update](https://polkadot.network/blog/staking-update-february-2022/). //! //! This pallet works of the basis of `on_idle`, meaning that it provides no guarantee about when it //! will succeed, if at all. Moreover, the queue implementation is unordered. In case of congestion, From bb1261d8c64096782dbf31e14bae70bb8001f6d0 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sun, 11 Sep 2022 15:01:55 +0700 Subject: [PATCH 53/81] abstract types --- frame/fast-unstake/src/lib.rs | 101 ++------------------------- frame/fast-unstake/src/tests.rs | 4 +- frame/fast-unstake/src/types.rs | 118 ++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 98 deletions(-) create mode 100644 frame/fast-unstake/src/types.rs diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 7dea0aea00427..8a9c03460982f 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -55,11 +55,14 @@ pub use pallet::*; #[cfg(test)] mod mock; + #[cfg(test)] mod tests; + // NOTE: enable benchmarking in tests as well. #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +mod types; pub mod weights; pub const LOG_TARGET: &'static str = "runtime::fast-unstake"; @@ -78,27 +81,20 @@ macro_rules! log { #[frame_support::pallet] pub mod pallet { use super::*; + use crate::types::*; use frame_election_provider_support::ElectionProvider; - use frame_support::{ - pallet_prelude::*, - traits::{Currency, IsSubType}, - }; + use frame_support::pallet_prelude::*; use frame_system::{pallet_prelude::*, RawOrigin}; use pallet_nomination_pools::PoolId; use pallet_staking::Pallet as Staking; use sp_runtime::{ traits::{Saturating, Zero}, - transaction_validity::{InvalidTransaction, TransactionValidityError}, DispatchResult, }; use sp_staking::EraIndex; use sp_std::{prelude::*, vec::Vec}; use weights::WeightInfo; - type BalanceOf = <::Currency as Currency< - ::AccountId, - >>::Balance; - #[pallet::pallet] pub struct Pallet(_); @@ -126,19 +122,6 @@ pub mod pallet { type WeightInfo: WeightInfo; } - /// An unstake request. - #[derive( - Encode, Decode, Eq, PartialEq, Clone, scale_info::TypeInfo, frame_support::RuntimeDebug, - )] - pub struct UnstakeRequest { - /// Their stash account. - pub(crate) stash: AccountId, - /// The list of eras for which they have been checked. - pub(crate) checked: Vec, - /// The pool they wish to join, if any. - pub(crate) maybe_pool_id: Option, - } - /// The current "head of the queue" being unstaked. #[pallet::storage] #[pallet::unbounded] @@ -481,78 +464,4 @@ pub mod pallet { }) } } - - #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo, RuntimeDebugNoBound)] - #[scale_info(skip_type_params(T))] - pub struct PreventStakingOpsIfUnbonding( - sp_std::marker::PhantomData, - ); - - #[cfg(test)] - impl PreventStakingOpsIfUnbonding { - pub fn new() -> Self { - Self(Default::default()) - } - } - - impl sp_runtime::traits::SignedExtension - for PreventStakingOpsIfUnbonding - where - ::Call: IsSubType>, - { - type AccountId = T::AccountId; - type Call = ::Call; - type AdditionalSigned = (); - type Pre = (); - const IDENTIFIER: &'static str = "PreventStakingOpsIfUnbonding"; - - fn additional_signed(&self) -> Result { - Ok(()) - } - - fn pre_dispatch( - self, - // NOTE: we want to prevent this stash-controller pair from doing anything in the - // staking system as long as they are registered here. - stash_or_controller: &Self::AccountId, - call: &Self::Call, - _info: &sp_runtime::traits::DispatchInfoOf, - _len: usize, - ) -> Result { - // we don't check this in the tx-pool as it requires a storage read. - if >>::is_sub_type(call).is_some() { - let check_stash = |stash: &T::AccountId| { - if Queue::::contains_key(&stash) || - Head::::get().map_or(false, |u| &u.stash == stash) - { - Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) - } else { - Ok(()) - } - }; - match ( - // mapped from controller. - pallet_staking::Ledger::::get(&stash_or_controller), - // mapped from stash. - pallet_staking::Bonded::::get(&stash_or_controller), - ) { - (Some(ledger), None) => { - // it is a controller. - check_stash(&ledger.stash) - }, - (_, Some(_)) => { - // it is a stash. - let stash = stash_or_controller; - check_stash(stash) - }, - (None, None) => { - // They are not a staker -- let them execute. - Ok(()) - }, - } - } else { - Ok(()) - } - } - } } diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 8f1e05a9928dc..5cc3e1b0d6c1d 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -18,7 +18,7 @@ //! Tests for pallet-fast-unstake. use super::*; -use crate::{mock::*, weights::WeightInfo, Event}; +use crate::{mock::*, types::*, weights::WeightInfo, Event}; use frame_support::{assert_noop, assert_ok, pallet_prelude::*, traits::Currency}; use pallet_nomination_pools::{BondedPools, LastPoolId, RewardPools}; use pallet_staking::{CurrentEra, IndividualExposure, RewardDestination}; @@ -830,7 +830,7 @@ mod on_idle { mod signed_extension { use super::*; - use crate::PreventStakingOpsIfUnbonding; + use crate::types::PreventStakingOpsIfUnbonding; use sp_runtime::traits::SignedExtension; const STAKING_CALL: crate::mock::Call = diff --git a/frame/fast-unstake/src/types.rs b/frame/fast-unstake/src/types.rs new file mode 100644 index 0000000000000..2fbb7e837fe9b --- /dev/null +++ b/frame/fast-unstake/src/types.rs @@ -0,0 +1,118 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Types used in the Fast Unstake pallet. + +use crate::*; +use codec::{Decode, Encode}; +use frame_support::{ + traits::{Currency, IsSubType}, + RuntimeDebugNoBound, +}; +use pallet_nomination_pools::PoolId; +use scale_info::TypeInfo; +use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError}; +use sp_staking::EraIndex; + +pub type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + +/// An unstake request. +#[derive( + Encode, Decode, Eq, PartialEq, Clone, scale_info::TypeInfo, frame_support::RuntimeDebug, +)] +pub struct UnstakeRequest { + /// Their stash account. + pub(crate) stash: AccountId, + /// The list of eras for which they have been checked. + pub(crate) checked: Vec, + /// The pool they wish to join, if any. + pub(crate) maybe_pool_id: Option, +} + +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo, RuntimeDebugNoBound)] +#[scale_info(skip_type_params(T))] +pub struct PreventStakingOpsIfUnbonding(sp_std::marker::PhantomData); + +#[cfg(test)] +impl PreventStakingOpsIfUnbonding { + pub fn new() -> Self { + Self(Default::default()) + } +} + +impl sp_runtime::traits::SignedExtension + for PreventStakingOpsIfUnbonding +where + ::Call: IsSubType>, +{ + type AccountId = T::AccountId; + type Call = ::Call; + type AdditionalSigned = (); + type Pre = (); + const IDENTIFIER: &'static str = "PreventStakingOpsIfUnbonding"; + + fn additional_signed(&self) -> Result { + Ok(()) + } + + fn pre_dispatch( + self, + // NOTE: we want to prevent this stash-controller pair from doing anything in the + // staking system as long as they are registered here. + stash_or_controller: &Self::AccountId, + call: &Self::Call, + _info: &sp_runtime::traits::DispatchInfoOf, + _len: usize, + ) -> Result { + // we don't check this in the tx-pool as it requires a storage read. + if >>::is_sub_type(call).is_some() { + let check_stash = |stash: &T::AccountId| { + if Queue::::contains_key(&stash) || + Head::::get().map_or(false, |u| &u.stash == stash) + { + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + } else { + Ok(()) + } + }; + match ( + // mapped from controller. + pallet_staking::Ledger::::get(&stash_or_controller), + // mapped from stash. + pallet_staking::Bonded::::get(&stash_or_controller), + ) { + (Some(ledger), None) => { + // it is a controller. + check_stash(&ledger.stash) + }, + (_, Some(_)) => { + // it is a stash. + let stash = stash_or_controller; + check_stash(stash) + }, + (None, None) => { + // They are not a staker -- let them execute. + Ok(()) + }, + } + } else { + Ok(()) + } + } +} From aed73b6050e41249cf8bbf69b4022705f4beca47 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sun, 11 Sep 2022 15:19:42 +0700 Subject: [PATCH 54/81] rm use --- frame/fast-unstake/src/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 5cc3e1b0d6c1d..22d772393e7a1 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -830,7 +830,6 @@ mod on_idle { mod signed_extension { use super::*; - use crate::types::PreventStakingOpsIfUnbonding; use sp_runtime::traits::SignedExtension; const STAKING_CALL: crate::mock::Call = From 1f320542c4dec5d9932040676fbcd0023d989f59 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sun, 11 Sep 2022 22:02:51 +0700 Subject: [PATCH 55/81] import --- frame/fast-unstake/src/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/fast-unstake/src/types.rs b/frame/fast-unstake/src/types.rs index 2fbb7e837fe9b..1cebd79822384 100644 --- a/frame/fast-unstake/src/types.rs +++ b/frame/fast-unstake/src/types.rs @@ -27,6 +27,7 @@ use pallet_nomination_pools::PoolId; use scale_info::TypeInfo; use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError}; use sp_staking::EraIndex; +use sp_std::{prelude::*, vec::Vec}; pub type BalanceOf = <::Currency as Currency< ::AccountId, @@ -83,8 +84,7 @@ where // we don't check this in the tx-pool as it requires a storage read. if >>::is_sub_type(call).is_some() { let check_stash = |stash: &T::AccountId| { - if Queue::::contains_key(&stash) || - Head::::get().map_or(false, |u| &u.stash == stash) + if Queue::::contains_key(&stash) || Head::::get().map_or(false, |u| &u.stash == stash) { Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) } else { From 5bb83acced453361104313e5307979fa9136228e Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 13 Sep 2022 14:18:55 +0100 Subject: [PATCH 56/81] Update frame/nomination-pools/benchmarking/src/lib.rs Co-authored-by: Nitwit <47109040+nitwit69@users.noreply.github.com> --- frame/nomination-pools/benchmarking/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 6d0b7376c400e..fa311ffe6ea30 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -490,7 +490,7 @@ frame_benchmarking::benchmarks! { // Give the depositor some balance to bond CurrencyOf::::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); - // Make sure no Pools exist aT a pre-condition for our verify checks + // Make sure no Pools exist at a pre-condition for our verify checks assert_eq!(RewardPools::::count(), 0); assert_eq!(BondedPools::::count(), 0); From 2325c23509003d25a0771d3b1bf1f35a2ae8d6f3 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 13 Sep 2022 14:19:06 +0100 Subject: [PATCH 57/81] Update frame/fast-unstake/src/types.rs Co-authored-by: Nitwit <47109040+nitwit69@users.noreply.github.com> --- frame/fast-unstake/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/types.rs b/frame/fast-unstake/src/types.rs index 1cebd79822384..a88fe304a37e5 100644 --- a/frame/fast-unstake/src/types.rs +++ b/frame/fast-unstake/src/types.rs @@ -102,7 +102,7 @@ where check_stash(&ledger.stash) }, (_, Some(_)) => { - // it is a stash. + // it's a stash. let stash = stash_or_controller; check_stash(stash) }, From 01b360547c87e2f38b775d62065e0dc9b75338e0 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 13 Sep 2022 13:20:02 +0000 Subject: [PATCH 58/81] Fix build --- bin/node/runtime/src/lib.rs | 2 +- frame/fast-unstake/src/benchmarking.rs | 4 ++-- frame/fast-unstake/src/lib.rs | 4 ++-- frame/fast-unstake/src/mock.rs | 14 +++++++------- frame/fast-unstake/src/tests.rs | 4 ++-- frame/fast-unstake/src/types.rs | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index b1797153af1fb..ef20839f6eb0d 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -575,7 +575,7 @@ impl pallet_staking::Config for Runtime { } impl pallet_fast_unstake::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type SlashPerEra = ConstU128<{ DOLLARS }>; type ControlOrigin = frame_system::EnsureRoot; type WeightInfo = (); diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index 2c7a064428afb..cf8a169b9964c 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -19,7 +19,7 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::{Pallet as FastUnstake, *}; +use crate::{Pallet as FastUnstake, *, types::*}; use frame_benchmarking::{benchmarks, whitelist_account}; use frame_support::{ assert_ok, @@ -72,7 +72,7 @@ pub(crate) fn fast_unstake_events() -> Vec> { frame_system::Pallet::::events() .into_iter() .map(|r| r.event) - .filter_map(|e| ::Event::from(e).try_into().ok()) + .filter_map(|e| ::RuntimeEvent::from(e).try_into().ok()) .collect::>() } diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 8a9c03460982f..84381779d0355 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -106,8 +106,8 @@ pub mod pallet { > + pallet_nomination_pools::Config { /// The overarching event type. - type Event: From> - + IsType<::Event> + type RuntimeEvent: From> + + IsType<::RuntimeEvent> + TryInto>; /// The amount of balance slashed per each era that was wastefully checked. diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index cb5a9431fed79..6d78f94736537 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -52,13 +52,13 @@ impl frame_system::Config for Runtime { type Origin = Origin; type Index = AccountIndex; type BlockNumber = BlockNumber; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Header = sp_runtime::testing::Header; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); type PalletInfo = PalletInfo; @@ -87,7 +87,7 @@ impl pallet_balances::Config for Runtime { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = Balance; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; @@ -135,7 +135,7 @@ impl pallet_staking::Config for Runtime { type UnixTime = pallet_timestamp::Pallet; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type RewardRemainder = (); - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Slash = (); type Reward = (); type SessionsPerEra = (); @@ -178,7 +178,7 @@ parameter_types! { } impl pallet_nomination_pools::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type Currency = Balances; type CurrencyBalance = Balance; @@ -198,7 +198,7 @@ parameter_types! { } impl fast_unstake::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type SlashPerEra = SlashPerEra; type ControlOrigin = frame_system::EnsureRoot; type WeightInfo = (); @@ -229,7 +229,7 @@ pub(crate) fn fast_unstake_events_since_last_call() -> Vec let events = System::events() .into_iter() .map(|r| r.event) - .filter_map(|e| if let Event::FastUnstake(inner) = e { Some(inner) } else { None }) + .filter_map(|e| if let RuntimeEvent::FastUnstake(inner) = e { Some(inner) } else { None }) .collect::>(); let already_seen = FastUnstakeEvents::get(); FastUnstakeEvents::set(events.len() as u32); diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 22d772393e7a1..b13e64daf7f9b 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -832,8 +832,8 @@ mod signed_extension { use super::*; use sp_runtime::traits::SignedExtension; - const STAKING_CALL: crate::mock::Call = - crate::mock::Call::Staking(pallet_staking::Call::::chill {}); + const STAKING_CALL: crate::mock::RuntimeCall = + crate::mock::RuntimeCall::Staking(pallet_staking::Call::::chill {}); #[test] fn does_nothing_if_not_queued() { diff --git a/frame/fast-unstake/src/types.rs b/frame/fast-unstake/src/types.rs index 1cebd79822384..f17aa8b46a2fe 100644 --- a/frame/fast-unstake/src/types.rs +++ b/frame/fast-unstake/src/types.rs @@ -60,10 +60,10 @@ impl PreventStakingOpsIfUnbonding { impl sp_runtime::traits::SignedExtension for PreventStakingOpsIfUnbonding where - ::Call: IsSubType>, + ::RuntimeCall: IsSubType>, { type AccountId = T::AccountId; - type Call = ::Call; + type Call = ::RuntimeCall; type AdditionalSigned = (); type Pre = (); const IDENTIFIER: &'static str = "PreventStakingOpsIfUnbonding"; From 0621f965e4b275bc2726475622259df55c261619 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 13 Sep 2022 13:21:47 +0000 Subject: [PATCH 59/81] fmt --- frame/fast-unstake/src/benchmarking.rs | 2 +- frame/fast-unstake/src/types.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index cf8a169b9964c..4cc67bb7bc2bf 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -19,7 +19,7 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::{Pallet as FastUnstake, *, types::*}; +use crate::{types::*, Pallet as FastUnstake, *}; use frame_benchmarking::{benchmarks, whitelist_account}; use frame_support::{ assert_ok, diff --git a/frame/fast-unstake/src/types.rs b/frame/fast-unstake/src/types.rs index 69759a81b81d7..bedf409e480b9 100644 --- a/frame/fast-unstake/src/types.rs +++ b/frame/fast-unstake/src/types.rs @@ -84,7 +84,8 @@ where // we don't check this in the tx-pool as it requires a storage read. if >>::is_sub_type(call).is_some() { let check_stash = |stash: &T::AccountId| { - if Queue::::contains_key(&stash) || Head::::get().map_or(false, |u| &u.stash == stash) + if Queue::::contains_key(&stash) || + Head::::get().map_or(false, |u| &u.stash == stash) { Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) } else { From 1f0fb09329ecbdae5b77ef0c304c7dd8c4dfaa78 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 13 Sep 2022 19:37:16 +0100 Subject: [PATCH 60/81] Update frame/fast-unstake/src/lib.rs Co-authored-by: Keith Yeung --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 84381779d0355..e0f23a6c6917c 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -293,7 +293,7 @@ pub mod pallet { .max(::WeightInfo::on_idle_unstake()) }; let mut try_eras_to_check = eras_to_check_per_block; - while worse_weight(validator_count, try_eras_to_check).all_gt(remaining_weight) { + while worse_weight(validator_count, try_eras_to_check).any_gt(remaining_weight) { try_eras_to_check.saturating_dec(); if try_eras_to_check.is_zero() { log!(debug, "early existing because try_eras_to_check is zero"); From 69128f951030c649833afe2ff6ac3837d5d16c5b Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 15 Sep 2022 08:36:12 +0000 Subject: [PATCH 61/81] make bounded --- frame/fast-unstake/src/benchmarking.rs | 6 +- frame/fast-unstake/src/lib.rs | 42 +++++- frame/fast-unstake/src/tests.rs | 200 +++++++++++++++++++++---- frame/fast-unstake/src/types.rs | 14 +- 4 files changed, 218 insertions(+), 44 deletions(-) diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index 4cc67bb7bc2bf..fcba22236b67b 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -22,7 +22,7 @@ use crate::{types::*, Pallet as FastUnstake, *}; use frame_benchmarking::{benchmarks, whitelist_account}; use frame_support::{ - assert_ok, + assert_ok, bounded_vec, traits::{Currency, EnsureOrigin, Get, Hooks}, }; use frame_system::RawOrigin; @@ -143,7 +143,7 @@ benchmarks! { on_idle_full_block::(); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: who.clone(), checked: vec![0], maybe_pool_id: Some(pool_id) }) + Some(UnstakeRequest { stash: who.clone(), checked: bounded_vec![0], maybe_pool_id: Some(pool_id) }) ); } : { @@ -182,7 +182,7 @@ benchmarks! { on_idle_full_block::(); } verify { - let checked = (1..=u).rev().collect::>(); + let checked: frame_support::BoundedVec<_, _> = (1..=u).rev().collect::>().try_into().unwrap(); assert_eq!( Head::::get(), Some(UnstakeRequest { stash: who.clone(), checked, maybe_pool_id: None }) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 84381779d0355..968f793acda78 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -95,6 +95,16 @@ pub mod pallet { use sp_std::{prelude::*, vec::Vec}; use weights::WeightInfo; + #[derive(scale_info::TypeInfo, codec::Encode, codec::Decode, codec::MaxEncodedLen)] + #[codec(mel_bound(T: Config))] + #[scale_info(skip_type_params(T))] + pub struct MaxChecked(sp_std::marker::PhantomData); + impl frame_support::traits::Get for MaxChecked { + fn get() -> u32 { + ::BondingDuration::get() + 1 + } + } + #[pallet::pallet] pub struct Pallet(_); @@ -124,8 +134,8 @@ pub mod pallet { /// The current "head of the queue" being unstaked. #[pallet::storage] - #[pallet::unbounded] - pub type Head = StorageValue<_, UnstakeRequest, OptionQuery>; + pub type Head = + StorageValue<_, UnstakeRequest>, OptionQuery>; /// The map of all accounts wishing to be unstaked. /// @@ -156,6 +166,8 @@ pub mod pallet { /// Some internal error happened while migrating stash. They are removed as head as a /// consequence. Errored { stash: T::AccountId }, + /// An internal error happened. Operations will be paused now. + InternalError, } #[pallet::error] @@ -356,6 +368,10 @@ pub mod pallet { total_check_range ); + // prune all the old eras that we don't care about. This will help us keep the bound + // of `checked`. + checked.retain(|e| *e >= current_era.saturating_sub(bonding_duration)); + // remove eras that have already been checked, take a maximum of // final_eras_to_check. total_check_range @@ -447,10 +463,24 @@ pub mod pallet { log!(info, "slashed {:?} by {:?}", stash, amount); Self::deposit_event(Event::::Slashed { stash, amount }); } else { - // Not exposed in these two eras. - checked.extend(eras_to_check.clone()); - Head::::put(UnstakeRequest { stash: stash.clone(), checked, maybe_pool_id }); - Self::deposit_event(Event::::Checked { stash, eras: eras_to_check }); + // Not exposed in these eras. + match checked.try_extend(eras_to_check.clone().into_iter()) { + Ok(_) => { + Head::::put(UnstakeRequest { + stash: stash.clone(), + checked, + maybe_pool_id, + }); + Self::deposit_event(Event::::Checked { stash, eras: eras_to_check }); + }, + Err(_) => { + // don't put the head back in -- there is an internal error in the + // pallet. + frame_support::defensive!("`checked is pruned via retain above`"); + Self::deposit_event(Event::::InternalError); + ErasToCheckPerBlock::::put(0); + }, + } } ::WeightInfo::on_idle_check(validator_count * eras_checked) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index b13e64daf7f9b..0e542966aac92 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -19,7 +19,7 @@ use super::*; use crate::{mock::*, types::*, weights::WeightInfo, Event}; -use frame_support::{assert_noop, assert_ok, pallet_prelude::*, traits::Currency}; +use frame_support::{assert_noop, assert_ok, bounded_vec, pallet_prelude::*, traits::Currency}; use pallet_nomination_pools::{BondedPools, LastPoolId, RewardPools}; use pallet_staking::{CurrentEra, IndividualExposure, RewardDestination}; @@ -80,7 +80,11 @@ fn cannot_register_if_in_queue() { fn cannot_register_if_head() { ExtBuilder::default().build_and_execute(|| { // Insert some Head item for stash - Head::::put(UnstakeRequest { stash: 1.clone(), checked: vec![], maybe_pool_id: None }); + Head::::put(UnstakeRequest { + stash: 1.clone(), + checked: bounded_vec![], + maybe_pool_id: None, + }); // Controller attempts to regsiter assert_noop!( FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32)), @@ -138,7 +142,11 @@ fn cannot_deregister_already_head() { // Controller attempts to register, should fail assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); // Insert some Head item for stash. - Head::::put(UnstakeRequest { stash: 1.clone(), checked: vec![], maybe_pool_id: None }); + Head::::put(UnstakeRequest { + stash: 1.clone(), + checked: bounded_vec![], + maybe_pool_id: None, + }); // Controller attempts to deregister assert_noop!(FastUnstake::deregister(Origin::signed(2)), Error::::AlreadyHead); }); @@ -210,7 +218,7 @@ mod on_idle { ); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![3], maybe_pool_id: Some(1) }) + Some(UnstakeRequest { stash: 1, checked: bounded_vec![3], maybe_pool_id: Some(1) }) ); // when: another 1 era. @@ -222,11 +230,15 @@ mod on_idle { // then: assert_eq!( fast_unstake_events_since_last_call(), - vec![Event::Checked { stash: 1, eras: vec![2] }] + vec![Event::Checked { stash: 1, eras: bounded_vec![2] }] ); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![3, 2], maybe_pool_id: Some(1) }) + Some(UnstakeRequest { + stash: 1, + checked: bounded_vec![3, 2], + maybe_pool_id: Some(1) + }) ); // when: then 5 eras, we only need 2 more. @@ -250,7 +262,7 @@ mod on_idle { Head::::get(), Some(UnstakeRequest { stash: 1, - checked: vec![3, 2, 1, 0], + checked: bounded_vec![3, 2, 1, 0], maybe_pool_id: Some(1) }) ); @@ -266,7 +278,7 @@ mod on_idle { Head::::get(), Some(UnstakeRequest { stash: 1, - checked: vec![3, 2, 1, 0], + checked: bounded_vec![3, 2, 1, 0], maybe_pool_id: Some(1) }) ); @@ -314,7 +326,11 @@ mod on_idle { // then assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![3, 2, 1, 0], maybe_pool_id: None }) + Some(UnstakeRequest { + stash: 1, + checked: bounded_vec![3, 2, 1, 0], + maybe_pool_id: None + }) ); assert_eq!(Queue::::count(), 4); @@ -331,7 +347,11 @@ mod on_idle { // then assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 5, checked: vec![3, 2, 1, 0], maybe_pool_id: None }), + Some(UnstakeRequest { + stash: 5, + checked: bounded_vec![3, 2, 1, 0], + maybe_pool_id: None + }), ); assert_eq!(Queue::::count(), 3); @@ -416,7 +436,11 @@ mod on_idle { // assert head item present assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![3, 2, 1, 0], maybe_pool_id: None }) + Some(UnstakeRequest { + stash: 1, + checked: bounded_vec![3, 2, 1, 0], + maybe_pool_id: None + }) ); next_block(true); @@ -454,7 +478,7 @@ mod on_idle { Head::::get(), Some(UnstakeRequest { stash: 1, - checked: vec![3, 2, 1, 0], + checked: bounded_vec![3, 2, 1, 0], maybe_pool_id: Some(0) }) ); @@ -502,7 +526,7 @@ mod on_idle { Head::::get(), Some(UnstakeRequest { stash: 1, - checked: vec![3, 2, 1, 0], + checked: bounded_vec![3, 2, 1, 0], maybe_pool_id: Some(1) }) ); @@ -542,21 +566,29 @@ mod on_idle { // assert head item present assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![3], maybe_pool_id: Some(1) }) + Some(UnstakeRequest { stash: 1, checked: bounded_vec![3], maybe_pool_id: Some(1) }) ); next_block(true); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![3, 2], maybe_pool_id: Some(1) }) + Some(UnstakeRequest { + stash: 1, + checked: bounded_vec![3, 2], + maybe_pool_id: Some(1) + }) ); next_block(true); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![3, 2, 1], maybe_pool_id: Some(1) }) + Some(UnstakeRequest { + stash: 1, + checked: bounded_vec![3, 2, 1], + maybe_pool_id: Some(1) + }) ); next_block(true); @@ -565,7 +597,7 @@ mod on_idle { Head::::get(), Some(UnstakeRequest { stash: 1, - checked: vec![3, 2, 1, 0], + checked: bounded_vec![3, 2, 1, 0], maybe_pool_id: Some(1) }) ); @@ -589,6 +621,86 @@ mod on_idle { }); } + #[test] + fn old_checked_era_pruned() { + // the only scenario where checked era pruning (checked.retain) comes handy is a follows: + // the whole vector is full and at capacity and in the next call we are ready to unstake, + // but then a new era happens. + ExtBuilder::default().build_and_execute(|| { + // given + ErasToCheckPerBlock::::put(1); + CurrentEra::::put(BondingDuration::get()); + + // register for fast unstake + assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), None)); + assert_eq!(Queue::::get(1), Some(None)); + + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: bounded_vec![3], maybe_pool_id: None }) + ); + + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { stash: 1, checked: bounded_vec![3, 2], maybe_pool_id: None }) + ); + + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stash: 1, + checked: bounded_vec![3, 2, 1], + maybe_pool_id: None + }) + ); + + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stash: 1, + checked: bounded_vec![3, 2, 1, 0], + maybe_pool_id: None + }) + ); + + // when: a new era happens right before one is free. + CurrentEra::::put(CurrentEra::::get().unwrap() + 1); + ExtBuilder::register_stakers_for_era(CurrentEra::::get().unwrap()); + + // then + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stash: 1, + // note era 0 is pruned to keep the vector length sane. + checked: bounded_vec![3, 2, 1, 4], + maybe_pool_id: None + }) + ); + + next_block(true); + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::Checked { stash: 1, eras: vec![3] }, + Event::Checked { stash: 1, eras: vec![2] }, + Event::Checked { stash: 1, eras: vec![1] }, + Event::Checked { stash: 1, eras: vec![0] }, + Event::Checked { stash: 1, eras: vec![4] }, + Event::Unstaked { stash: 1, maybe_pool_id: None, result: Ok(()) } + ] + ); + assert_unstaked(&1); + }); + } + #[test] fn unstake_paused_mid_election() { ExtBuilder::default().build_and_execute(|| { @@ -603,13 +715,17 @@ mod on_idle { next_block(true); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![3], maybe_pool_id: Some(1) }) + Some(UnstakeRequest { stash: 1, checked: bounded_vec![3], maybe_pool_id: Some(1) }) ); next_block(true); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![3, 2], maybe_pool_id: Some(1) }) + Some(UnstakeRequest { + stash: 1, + checked: bounded_vec![3, 2], + maybe_pool_id: Some(1) + }) ); // when @@ -619,13 +735,21 @@ mod on_idle { next_block(true); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![3, 2], maybe_pool_id: Some(1) }) + Some(UnstakeRequest { + stash: 1, + checked: bounded_vec![3, 2], + maybe_pool_id: Some(1) + }) ); next_block(true); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![3, 2], maybe_pool_id: Some(1) }) + Some(UnstakeRequest { + stash: 1, + checked: bounded_vec![3, 2], + maybe_pool_id: Some(1) + }) ); // then we register a new era. @@ -637,7 +761,11 @@ mod on_idle { next_block(true); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![3, 2, 4], maybe_pool_id: Some(1) }) + Some(UnstakeRequest { + stash: 1, + checked: bounded_vec![3, 2, 4], + maybe_pool_id: Some(1) + }) ); // progress to end @@ -646,7 +774,7 @@ mod on_idle { Head::::get(), Some(UnstakeRequest { stash: 1, - checked: vec![3, 2, 4, 1], + checked: bounded_vec![3, 2, 4, 1], maybe_pool_id: Some(1) }) ); @@ -699,12 +827,20 @@ mod on_idle { next_block(true); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: exposed, checked: vec![3], maybe_pool_id: None }) + Some(UnstakeRequest { + stash: exposed, + checked: bounded_vec![3], + maybe_pool_id: None + }) ); next_block(true); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: exposed, checked: vec![3, 2], maybe_pool_id: None }) + Some(UnstakeRequest { + stash: exposed, + checked: bounded_vec![3, 2], + maybe_pool_id: None + }) ); next_block(true); assert_eq!(Head::::get(), None); @@ -751,7 +887,11 @@ mod on_idle { next_block(true); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: exposed, checked: vec![3, 2], maybe_pool_id: None }) + Some(UnstakeRequest { + stash: exposed, + checked: bounded_vec![3, 2], + maybe_pool_id: None + }) ); next_block(true); assert_eq!(Head::::get(), None); @@ -812,7 +952,11 @@ mod on_idle { next_block(true); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 42, checked: vec![3, 2, 1, 0], maybe_pool_id: None }) + Some(UnstakeRequest { + stash: 42, + checked: bounded_vec![3, 2, 1, 0], + maybe_pool_id: None + }) ); next_block(true); assert_eq!(Head::::get(), None); @@ -877,7 +1021,7 @@ mod signed_extension { assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: 1, checked: vec![3], maybe_pool_id: None }) + Some(UnstakeRequest { stash: 1, checked: bounded_vec![3], maybe_pool_id: None }) ); // then diff --git a/frame/fast-unstake/src/types.rs b/frame/fast-unstake/src/types.rs index bedf409e480b9..ae8702e56a842 100644 --- a/frame/fast-unstake/src/types.rs +++ b/frame/fast-unstake/src/types.rs @@ -18,16 +18,16 @@ //! Types used in the Fast Unstake pallet. use crate::*; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ - traits::{Currency, IsSubType}, - RuntimeDebugNoBound, + traits::{Currency, Get, IsSubType}, + BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; use pallet_nomination_pools::PoolId; use scale_info::TypeInfo; use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError}; use sp_staking::EraIndex; -use sp_std::{prelude::*, vec::Vec}; +use sp_std::{fmt::Debug, prelude::*}; pub type BalanceOf = <::Currency as Currency< ::AccountId, @@ -35,13 +35,13 @@ pub type BalanceOf = <::Currency as Currency< /// An unstake request. #[derive( - Encode, Decode, Eq, PartialEq, Clone, scale_info::TypeInfo, frame_support::RuntimeDebug, + Encode, Decode, EqNoBound, PartialEqNoBound, Clone, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen, )] -pub struct UnstakeRequest { +pub struct UnstakeRequest> { /// Their stash account. pub(crate) stash: AccountId, /// The list of eras for which they have been checked. - pub(crate) checked: Vec, + pub(crate) checked: BoundedVec, /// The pool they wish to join, if any. pub(crate) maybe_pool_id: Option, } From b6e51f3c960c75df37265542e7e9aafe27e8eaa2 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 15 Sep 2022 11:35:04 +0000 Subject: [PATCH 62/81] feedback from code review with Ankan --- frame/fast-unstake/src/benchmarking.rs | 4 +- frame/fast-unstake/src/lib.rs | 95 +++++++++++++------------- frame/fast-unstake/src/tests.rs | 2 +- 3 files changed, 52 insertions(+), 49 deletions(-) diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index fcba22236b67b..8e7e2a4d51bfc 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -22,7 +22,7 @@ use crate::{types::*, Pallet as FastUnstake, *}; use frame_benchmarking::{benchmarks, whitelist_account}; use frame_support::{ - assert_ok, bounded_vec, + assert_ok, traits::{Currency, EnsureOrigin, Get, Hooks}, }; use frame_system::RawOrigin; @@ -143,7 +143,7 @@ benchmarks! { on_idle_full_block::(); assert_eq!( Head::::get(), - Some(UnstakeRequest { stash: who.clone(), checked: bounded_vec![0], maybe_pool_id: Some(pool_id) }) + Some(UnstakeRequest { stash: who.clone(), checked: vec![0].try_into().unwrap(), maybe_pool_id: Some(pool_id) }) ); } : { diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 8028572236a7b..a27b9e5acf4de 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -190,7 +190,11 @@ pub mod pallet { #[pallet::hooks] impl Hooks for Pallet { fn on_idle(_: T::BlockNumber, remaining_weight: Weight) -> Weight { - Self::process_head(remaining_weight) + if remaining_weight.any_lt(T::DbWeight::get().reads(2)) { + return Weight::from_ref_time(0) + } + + Self::do_on_idle(remaining_weight) } } @@ -269,9 +273,9 @@ pub mod pallet { /// /// Dispatch origin must be signed by the [`Config::ControlOrigin`]. #[pallet::weight(::WeightInfo::control())] - pub fn control(origin: OriginFor, eras_to_check: EraIndex) -> DispatchResult { + pub fn control(origin: OriginFor, unchecked_eras_to_check: EraIndex) -> DispatchResult { let _ = T::ControlOrigin::ensure_origin(origin)?; - ErasToCheckPerBlock::::put(eras_to_check); + ErasToCheckPerBlock::::put(unchecked_eras_to_check); Ok(()) } } @@ -285,10 +289,9 @@ pub mod pallet { /// /// 1. We assume this is only ever called once per `on_idle`. This is because we know that /// in all use cases, even a single nominator cannot be unbonded in a single call. Multiple - /// calls to this function are thus not needed. 2. We will only mark a staker - #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))] - pub(crate) fn process_head(remaining_weight: Weight) -> Weight { - let eras_to_check_per_block = ErasToCheckPerBlock::::get(); + /// calls to this function are thus not needed. 2. We will only mark a staker. + pub(crate) fn do_on_idle(remaining_weight: Weight) -> Weight { + let mut eras_to_check_per_block = ErasToCheckPerBlock::::get(); if eras_to_check_per_block.is_zero() { return T::DbWeight::get().reads(1) } @@ -299,23 +302,17 @@ pub mod pallet { // determine the number of eras to check. This is based on both `ErasToCheckPerBlock` // and `remaining_weight` passed on to us from the runtime executive. - let final_eras_to_check = { - let worse_weight = |v, u| { - ::WeightInfo::on_idle_check(v * u) - .max(::WeightInfo::on_idle_unstake()) - }; - let mut try_eras_to_check = eras_to_check_per_block; - while worse_weight(validator_count, try_eras_to_check).any_gt(remaining_weight) { - try_eras_to_check.saturating_dec(); - if try_eras_to_check.is_zero() { - log!(debug, "early existing because try_eras_to_check is zero"); - return T::DbWeight::get().reads(1) - } - } - - drop(eras_to_check_per_block); - try_eras_to_check + let worse_weight = |v, u| { + ::WeightInfo::on_idle_check(v * u) + .max(::WeightInfo::on_idle_unstake()) }; + while worse_weight(validator_count, eras_to_check_per_block).any_gt(remaining_weight) { + eras_to_check_per_block.saturating_dec(); + if eras_to_check_per_block.is_zero() { + log!(debug, "early existing because eras_to_check_per_block is zero"); + return T::DbWeight::get().reads(2) + } + } if ::ElectionProvider::ongoing() { // NOTE: we assume `ongoing` does not consume any weight. @@ -327,8 +324,8 @@ pub mod pallet { let UnstakeRequest { stash, mut checked, maybe_pool_id } = match Head::::take() .or_else(|| { + // NOTE: there is no order guarantees in `Queue`. Queue::::drain() - .take(1) .map(|(stash, maybe_pool_id)| UnstakeRequest { stash, maybe_pool_id, @@ -345,17 +342,19 @@ pub mod pallet { log!( debug, - "checking {:?}, final_eras_to_check = {:?}, remaining_weight = {:?}", + "checking {:?}, eras_to_check_per_block = {:?}, remaining_weight = {:?}", stash, - final_eras_to_check, + eras_to_check_per_block, remaining_weight ); // the range that we're allowed to check in this round. let current_era = pallet_staking::CurrentEra::::get().unwrap_or_default(); - let eras_to_check = { - let bonding_duration = ::BondingDuration::get(); - + let bonding_duration = ::BondingDuration::get(); + // prune all the old eras that we don't care about. This will help us keep the bound + // of `checked`. + checked.retain(|e| *e >= current_era.saturating_sub(bonding_duration)); + let unchecked_eras_to_check = { // get the last available `bonding_duration` eras up to current era in reverse // order. let total_check_range = (current_era.saturating_sub(bonding_duration)..= @@ -368,22 +367,23 @@ pub mod pallet { total_check_range ); - // prune all the old eras that we don't care about. This will help us keep the bound - // of `checked`. - checked.retain(|e| *e >= current_era.saturating_sub(bonding_duration)); - // remove eras that have already been checked, take a maximum of - // final_eras_to_check. + // eras_to_check_per_block. total_check_range .into_iter() .filter(|e| !checked.contains(e)) - .take(final_eras_to_check as usize) + .take(eras_to_check_per_block as usize) .collect::>() }; - log!(debug, "{} eras to check: {:?}", eras_to_check.len(), eras_to_check); + log!( + debug, + "{} eras to check: {:?}", + unchecked_eras_to_check.len(), + unchecked_eras_to_check + ); - if eras_to_check.is_empty() { + if unchecked_eras_to_check.is_empty() { // `stash` is not exposed in any era now -- we can let go of them now. let num_slashing_spans = Staking::::slashing_spans(&stash).iter().count() as u32; @@ -427,13 +427,13 @@ pub mod pallet { maybe_pool_id, result ); - Self::deposit_event(Event::::Unstaked { stash, maybe_pool_id, result }); + Self::deposit_event(Event::::Unstaked { stash, maybe_pool_id, result }); ::WeightInfo::on_idle_unstake() } else { // eras remaining to be checked. let mut eras_checked = 0u32; - let is_exposed = eras_to_check.iter().any(|e| { + let is_exposed = unchecked_eras_to_check.iter().any(|e| { eras_checked.saturating_inc(); Self::is_exposed_in_era(&stash, e) }); @@ -444,7 +444,7 @@ pub mod pallet { eras_checked, is_exposed, validator_count, - eras_to_check.len() + unchecked_eras_to_check.len() ); // NOTE: you can be extremely unlucky and get slashed here: You are not exposed in @@ -464,21 +464,24 @@ pub mod pallet { Self::deposit_event(Event::::Slashed { stash, amount }); } else { // Not exposed in these eras. - match checked.try_extend(eras_to_check.clone().into_iter()) { + match checked.try_extend(unchecked_eras_to_check.clone().into_iter()) { Ok(_) => { Head::::put(UnstakeRequest { stash: stash.clone(), checked, maybe_pool_id, }); - Self::deposit_event(Event::::Checked { stash, eras: eras_to_check }); + Self::deposit_event(Event::::Checked { + stash, + eras: unchecked_eras_to_check, + }); }, Err(_) => { // don't put the head back in -- there is an internal error in the // pallet. frame_support::defensive!("`checked is pruned via retain above`"); - Self::deposit_event(Event::::InternalError); ErasToCheckPerBlock::::put(0); + Self::deposit_event(Event::::InternalError); }, } } @@ -487,10 +490,10 @@ pub mod pallet { } } - /// Checks whether an account `who` has been exposed in an era. - fn is_exposed_in_era(who: &T::AccountId, era: &EraIndex) -> bool { + /// Checks whether an account `staker` has been exposed in an era. + fn is_exposed_in_era(staker: &T::AccountId, era: &EraIndex) -> bool { pallet_staking::ErasStakers::::iter_prefix(era).any(|(validator, exposures)| { - validator == *who || exposures.others.iter().any(|i| i.who == *who) + validator == *staker || exposures.others.iter().any(|i| i.who == *staker) }) } } diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 0e542966aac92..f650869cb519e 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -389,8 +389,8 @@ mod on_idle { next_block(true); // confirm Head / Queue items remaining - assert_eq!(Head::::get(), None); assert_eq!(Queue::::count(), 1); + assert_eq!(Head::::get(), None); // process on idle and check eras for next Queue item next_block(true); From 2720ab00d9b34717a94fbf3438ec66bc2aa7125e Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 16 Sep 2022 16:42:32 +0100 Subject: [PATCH 63/81] Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index a27b9e5acf4de..125639979e1f9 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -30,7 +30,7 @@ //! //! This pallet works of the basis of `on_idle`, meaning that it provides no guarantee about when it //! will succeed, if at all. Moreover, the queue implementation is unordered. In case of congestion, -//! not FIFO ordering is provided. +//! no FIFO ordering is provided. //! //! Stakers who are certain about NOT being exposed can register themselves with //! [`Call::register_fast_unstake`]. This will chill, and fully unbond the staker, and place them in From 002fb1cff516b6793b698a966fe8dfcacb28c952 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 16 Sep 2022 16:42:44 +0100 Subject: [PATCH 64/81] Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 125639979e1f9..2a921d361cafd 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -28,7 +28,7 @@ //! Nominator" role explained in the //! [February Staking Update](https://polkadot.network/blog/staking-update-february-2022/). //! -//! This pallet works of the basis of `on_idle`, meaning that it provides no guarantee about when it +//! This pallet works off the basis of `on_idle`, meaning that it provides no guarantee about when it //! will succeed, if at all. Moreover, the queue implementation is unordered. In case of congestion, //! no FIFO ordering is provided. //! From 8cbcfd89afffed7ecdef889417aec0848458c45b Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 16 Sep 2022 16:43:00 +0100 Subject: [PATCH 65/81] Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 2a921d361cafd..21c6483bfa268 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! A pallet that's designed to ONLY do: +//! A pallet that's designed to JUST do the following: //! //! If a nominator is not exposed at all in any `ErasStakers` (i.e. "has not actively backed any //! validators in the last `BondingDuration` days"), then they can register themselves in this From 88dc2e5327c8eaa69d871052083abcfc6f1702ab Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 16 Sep 2022 16:43:16 +0100 Subject: [PATCH 66/81] Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 21c6483bfa268..10a436bf76026 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -17,7 +17,7 @@ //! A pallet that's designed to JUST do the following: //! -//! If a nominator is not exposed at all in any `ErasStakers` (i.e. "has not actively backed any +//! If a nominator is not exposed in any `ErasStakers` (i.e. "has not actively backed any //! validators in the last `BondingDuration` days"), then they can register themselves in this //! pallet, unstake faster than needing to wait an entire bonding duration, and potentially move //! into a nomination pool. From 40b8afea67e3f15be03fef5b216881859e997512 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 16 Sep 2022 16:43:30 +0100 Subject: [PATCH 67/81] Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 10a436bf76026..4c9f2069d6334 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -19,7 +19,7 @@ //! //! If a nominator is not exposed in any `ErasStakers` (i.e. "has not actively backed any //! validators in the last `BondingDuration` days"), then they can register themselves in this -//! pallet, unstake faster than needing to wait an entire bonding duration, and potentially move +//! pallet, unstake faster than having to wait an entire bonding duration, and potentially move //! into a nomination pool. //! //! Appearing in the exposure of a validator means being exposed equal to that validator from the From cc30c6b72bc719fe9be83657557d76bdf9ed2f14 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 16 Sep 2022 16:43:38 +0100 Subject: [PATCH 68/81] Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 4c9f2069d6334..e719a10f13838 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -39,7 +39,7 @@ //! Once queued, but not being actively processed, stakers can withdraw their request via //! [`Call::deregister`]. //! -//! Once queued, a staker wishing to unbond can perform to further action in pallet-staking. This is +//! Once queued, a staker wishing to unbond can perform no further action in pallet-staking. This is //! to prevent them from accidentally exposing themselves behind a validator etc. //! //! Once processed, if successful, no additional fees for the checking process is taken, and the From 998775129c4a0e64f983713a7e2043ffc4a73fe2 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 16 Sep 2022 16:43:58 +0100 Subject: [PATCH 69/81] Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index e719a10f13838..e7ba410e57a72 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -247,7 +247,7 @@ pub mod pallet { Ok(()) } - /// Deregister oneself from the fast-unstake (and possibly joining a pool). + /// Deregister oneself from the fast-unstake (also cancels joining the pool if that was supplied on `register_fast_unstake` . /// /// This is useful if one is registered, they are still waiting, and they change their mind. /// From c807a954b9026ce99826f213da23faa79415c9d2 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 16 Sep 2022 16:44:07 +0100 Subject: [PATCH 70/81] Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index e7ba410e57a72..f75902a466f84 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -42,7 +42,7 @@ //! Once queued, a staker wishing to unbond can perform no further action in pallet-staking. This is //! to prevent them from accidentally exposing themselves behind a validator etc. //! -//! Once processed, if successful, no additional fees for the checking process is taken, and the +//! Once processed, if successful, no additional fee for the checking process is taken, and the //! staker is instantly unbonded. Optionally, if the have asked to join a pool, their *entire* stake //! is joined into their pool of choice. //! From daf38eb4573493f06ee283ab4045cdbb257667db Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 16 Sep 2022 16:44:15 +0100 Subject: [PATCH 71/81] Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov --- frame/fast-unstake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index f75902a466f84..023dec2a9ebad 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -43,7 +43,7 @@ //! to prevent them from accidentally exposing themselves behind a validator etc. //! //! Once processed, if successful, no additional fee for the checking process is taken, and the -//! staker is instantly unbonded. Optionally, if the have asked to join a pool, their *entire* stake +//! staker is instantly unbonded. Optionally, if they have asked to join a pool, their *entire* stake //! is joined into their pool of choice. //! //! If unsuccessful, meaning that the staker was exposed sometime in the last `BondingDuration` eras From 31a3948e0260c6f38ec9a8ef2e37825173bd764f Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 16 Sep 2022 16:45:18 +0100 Subject: [PATCH 72/81] Update frame/fast-unstake/src/mock.rs --- frame/fast-unstake/src/mock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 6d78f94736537..3ff639282ea5b 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); From 10b9c45869517aeec4a7506259c936e137e33761 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 20 Sep 2022 09:11:35 +0000 Subject: [PATCH 73/81] update to master --- frame/fast-unstake/src/benchmarking.rs | 2 +- frame/fast-unstake/src/lib.rs | 10 ++--- frame/fast-unstake/src/mock.rs | 1 + frame/fast-unstake/src/tests.rs | 54 +++++++++++++------------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index 8e7e2a4d51bfc..a50a9f39069df 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -189,7 +189,7 @@ benchmarks! { ); assert!(matches!( fast_unstake_events::().last(), - Some(Event::Checked { .. }) + Some(Event::Checking { .. }) )); } diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index a27b9e5acf4de..7fd8117c40b26 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -98,8 +98,8 @@ pub mod pallet { #[derive(scale_info::TypeInfo, codec::Encode, codec::Decode, codec::MaxEncodedLen)] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] - pub struct MaxChecked(sp_std::marker::PhantomData); - impl frame_support::traits::Get for MaxChecked { + pub struct MaxChecking(sp_std::marker::PhantomData); + impl frame_support::traits::Get for MaxChecking { fn get() -> u32 { ::BondingDuration::get() + 1 } @@ -135,7 +135,7 @@ pub mod pallet { /// The current "head of the queue" being unstaked. #[pallet::storage] pub type Head = - StorageValue<_, UnstakeRequest>, OptionQuery>; + StorageValue<_, UnstakeRequest>, OptionQuery>; /// The map of all accounts wishing to be unstaked. /// @@ -162,7 +162,7 @@ pub mod pallet { /// A staker was slashed for requesting fast-unstake whilst being exposed. Slashed { stash: T::AccountId, amount: BalanceOf }, /// A staker was partially checked for the given eras, but the process did not finish. - Checked { stash: T::AccountId, eras: Vec }, + Checking { stash: T::AccountId, eras: Vec }, /// Some internal error happened while migrating stash. They are removed as head as a /// consequence. Errored { stash: T::AccountId }, @@ -471,7 +471,7 @@ pub mod pallet { checked, maybe_pool_id, }); - Self::deposit_event(Event::::Checked { + Self::deposit_event(Event::::Checking { stash, eras: unchecked_eras_to_check, }); diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 6d78f94736537..f26c156c88692 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -150,6 +150,7 @@ impl pallet_staking::Config for Runtime { type ElectionProvider = MockElection; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index f650869cb519e..a3264e15f9165 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -214,7 +214,7 @@ mod on_idle { // then assert_eq!( fast_unstake_events_since_last_call(), - vec![Event::Checked { stash: 1, eras: vec![3] }] + vec![Event::Checking { stash: 1, eras: vec![3] }] ); assert_eq!( Head::::get(), @@ -230,7 +230,7 @@ mod on_idle { // then: assert_eq!( fast_unstake_events_since_last_call(), - vec![Event::Checked { stash: 1, eras: bounded_vec![2] }] + vec![Event::Checking { stash: 1, eras: bounded_vec![2] }] ); assert_eq!( Head::::get(), @@ -256,7 +256,7 @@ mod on_idle { // then: assert_eq!( fast_unstake_events_since_last_call(), - vec![Event::Checked { stash: 1, eras: vec![1, 0] }] + vec![Event::Checking { stash: 1, eras: vec![1, 0] }] ); assert_eq!( Head::::get(), @@ -358,9 +358,9 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checked { stash: 1, eras: vec![3, 2, 1, 0] }, + Event::Checking { stash: 1, eras: vec![3, 2, 1, 0] }, Event::Unstaked { stash: 1, maybe_pool_id: None, result: Ok(()) }, - Event::Checked { stash: 5, eras: vec![3, 2, 1, 0] } + Event::Checking { stash: 5, eras: vec![3, 2, 1, 0] } ] ); }); @@ -405,9 +405,9 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checked { stash: 1, eras: vec![3, 2, 1, 0] }, + Event::Checking { stash: 1, eras: vec![3, 2, 1, 0] }, Event::Unstaked { stash: 1, maybe_pool_id: Some(1), result: Ok(()) }, - Event::Checked { stash: 3, eras: vec![3, 2, 1, 0] }, + Event::Checking { stash: 3, eras: vec![3, 2, 1, 0] }, Event::Unstaked { stash: 3, maybe_pool_id: Some(1), result: Ok(()) }, ] ); @@ -449,7 +449,7 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checked { stash: 1, eras: vec![3, 2, 1, 0] }, + Event::Checking { stash: 1, eras: vec![3, 2, 1, 0] }, Event::Unstaked { stash: 1, maybe_pool_id: None, result: Ok(()) } ] ); @@ -489,7 +489,7 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checked { stash: 1, eras: vec![3, 2, 1, 0] }, + Event::Checking { stash: 1, eras: vec![3, 2, 1, 0] }, Event::Unstaked { stash: 1, maybe_pool_id: Some(0), @@ -537,7 +537,7 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checked { stash: 1, eras: vec![3, 2, 1, 0] }, + Event::Checking { stash: 1, eras: vec![3, 2, 1, 0] }, Event::Unstaked { stash: 1, maybe_pool_id: Some(1), result: Ok(()) } ] ); @@ -609,10 +609,10 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checked { stash: 1, eras: vec![3] }, - Event::Checked { stash: 1, eras: vec![2] }, - Event::Checked { stash: 1, eras: vec![1] }, - Event::Checked { stash: 1, eras: vec![0] }, + Event::Checking { stash: 1, eras: vec![3] }, + Event::Checking { stash: 1, eras: vec![2] }, + Event::Checking { stash: 1, eras: vec![1] }, + Event::Checking { stash: 1, eras: vec![0] }, Event::Unstaked { stash: 1, maybe_pool_id: Some(1), result: Ok(()) } ] ); @@ -689,11 +689,11 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checked { stash: 1, eras: vec![3] }, - Event::Checked { stash: 1, eras: vec![2] }, - Event::Checked { stash: 1, eras: vec![1] }, - Event::Checked { stash: 1, eras: vec![0] }, - Event::Checked { stash: 1, eras: vec![4] }, + Event::Checking { stash: 1, eras: vec![3] }, + Event::Checking { stash: 1, eras: vec![2] }, + Event::Checking { stash: 1, eras: vec![1] }, + Event::Checking { stash: 1, eras: vec![0] }, + Event::Checking { stash: 1, eras: vec![4] }, Event::Unstaked { stash: 1, maybe_pool_id: None, result: Ok(()) } ] ); @@ -786,10 +786,10 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checked { stash: 1, eras: vec![3] }, - Event::Checked { stash: 1, eras: vec![2] }, - Event::Checked { stash: 1, eras: vec![4] }, - Event::Checked { stash: 1, eras: vec![1] }, + Event::Checking { stash: 1, eras: vec![3] }, + Event::Checking { stash: 1, eras: vec![2] }, + Event::Checking { stash: 1, eras: vec![4] }, + Event::Checking { stash: 1, eras: vec![1] }, Event::Unstaked { stash: 1, maybe_pool_id: Some(1), result: Ok(()) } ] ); @@ -849,8 +849,8 @@ mod on_idle { fast_unstake_events_since_last_call(), // we slash them by 21, since we checked 3 eras in total (3, 2, 1). vec![ - Event::Checked { stash: exposed, eras: vec![3] }, - Event::Checked { stash: exposed, eras: vec![2] }, + Event::Checking { stash: exposed, eras: vec![3] }, + Event::Checking { stash: exposed, eras: vec![2] }, Event::Slashed { stash: exposed, amount: 3 * 7 } ] ); @@ -900,7 +900,7 @@ mod on_idle { fast_unstake_events_since_last_call(), // we slash them by 28, since we checked 4 eras in total. vec![ - Event::Checked { stash: exposed, eras: vec![3, 2] }, + Event::Checking { stash: exposed, eras: vec![3, 2] }, Event::Slashed { stash: exposed, amount: 4 * 7 } ] ); @@ -964,7 +964,7 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checked { stash: 42, eras: vec![3, 2, 1, 0] }, + Event::Checking { stash: 42, eras: vec![3, 2, 1, 0] }, Event::Unstaked { stash: 42, maybe_pool_id: None, result: Ok(()) } ] ); From f37f466a812ce77272245c7d58293ae61dda1fd9 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 20 Sep 2022 09:19:00 +0000 Subject: [PATCH 74/81] some final review comments --- frame/fast-unstake/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 7fd8117c40b26..abea0e5566aac 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -289,7 +289,9 @@ pub mod pallet { /// /// 1. We assume this is only ever called once per `on_idle`. This is because we know that /// in all use cases, even a single nominator cannot be unbonded in a single call. Multiple - /// calls to this function are thus not needed. 2. We will only mark a staker. + /// calls to this function are thus not needed. + /// + /// 2. We will only mark a staker as unstaked if at the beginning of a check cycle, they are found out to have no eras to check. At the end of a check cycle, even if they are fully checked, we don't finish the process. pub(crate) fn do_on_idle(remaining_weight: Weight) -> Weight { let mut eras_to_check_per_block = ErasToCheckPerBlock::::get(); if eras_to_check_per_block.is_zero() { @@ -302,11 +304,11 @@ pub mod pallet { // determine the number of eras to check. This is based on both `ErasToCheckPerBlock` // and `remaining_weight` passed on to us from the runtime executive. - let worse_weight = |v, u| { + let max_weight = |v, u| { ::WeightInfo::on_idle_check(v * u) .max(::WeightInfo::on_idle_unstake()) }; - while worse_weight(validator_count, eras_to_check_per_block).any_gt(remaining_weight) { + while max_weight(validator_count, eras_to_check_per_block).any_gt(remaining_weight) { eras_to_check_per_block.saturating_dec(); if eras_to_check_per_block.is_zero() { log!(debug, "early existing because eras_to_check_per_block is zero"); From ea921422a9342210bf83970fa18613c7b18caac2 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 20 Sep 2022 09:19:13 +0000 Subject: [PATCH 75/81] fmt --- frame/fast-unstake/src/lib.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 022970d752761..7e538352c11d1 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -28,9 +28,9 @@ //! Nominator" role explained in the //! [February Staking Update](https://polkadot.network/blog/staking-update-february-2022/). //! -//! This pallet works off the basis of `on_idle`, meaning that it provides no guarantee about when it -//! will succeed, if at all. Moreover, the queue implementation is unordered. In case of congestion, -//! no FIFO ordering is provided. +//! This pallet works off the basis of `on_idle`, meaning that it provides no guarantee about when +//! it will succeed, if at all. Moreover, the queue implementation is unordered. In case of +//! congestion, no FIFO ordering is provided. //! //! Stakers who are certain about NOT being exposed can register themselves with //! [`Call::register_fast_unstake`]. This will chill, and fully unbond the staker, and place them in @@ -43,8 +43,8 @@ //! to prevent them from accidentally exposing themselves behind a validator etc. //! //! Once processed, if successful, no additional fee for the checking process is taken, and the -//! staker is instantly unbonded. Optionally, if they have asked to join a pool, their *entire* stake -//! is joined into their pool of choice. +//! staker is instantly unbonded. Optionally, if they have asked to join a pool, their *entire* +//! stake is joined into their pool of choice. //! //! If unsuccessful, meaning that the staker was exposed sometime in the last `BondingDuration` eras //! they will end up being slashed for the amount of wasted work they have inflicted on the chian. @@ -247,7 +247,8 @@ pub mod pallet { Ok(()) } - /// Deregister oneself from the fast-unstake (also cancels joining the pool if that was supplied on `register_fast_unstake` . + /// Deregister oneself from the fast-unstake (also cancels joining the pool if that was + /// supplied on `register_fast_unstake` . /// /// This is useful if one is registered, they are still waiting, and they change their mind. /// @@ -291,7 +292,9 @@ pub mod pallet { /// in all use cases, even a single nominator cannot be unbonded in a single call. Multiple /// calls to this function are thus not needed. /// - /// 2. We will only mark a staker as unstaked if at the beginning of a check cycle, they are found out to have no eras to check. At the end of a check cycle, even if they are fully checked, we don't finish the process. + /// 2. We will only mark a staker as unstaked if at the beginning of a check cycle, they are + /// found out to have no eras to check. At the end of a check cycle, even if they are fully + /// checked, we don't finish the process. pub(crate) fn do_on_idle(remaining_weight: Weight) -> Weight { let mut eras_to_check_per_block = ErasToCheckPerBlock::::get(); if eras_to_check_per_block.is_zero() { From eae079f7f69d937c94e5be2fa40b63118e9cdf25 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 20 Sep 2022 09:25:51 +0000 Subject: [PATCH 76/81] fix clippy --- frame/fast-unstake/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index a3264e15f9165..a635359b79add 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -81,7 +81,7 @@ fn cannot_register_if_head() { ExtBuilder::default().build_and_execute(|| { // Insert some Head item for stash Head::::put(UnstakeRequest { - stash: 1.clone(), + stash: 1, checked: bounded_vec![], maybe_pool_id: None, }); @@ -143,7 +143,7 @@ fn cannot_deregister_already_head() { assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); // Insert some Head item for stash. Head::::put(UnstakeRequest { - stash: 1.clone(), + stash: 1, checked: bounded_vec![], maybe_pool_id: None, }); From 5a8048a00e96db0360921ffd396499930b33a505 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 20 Sep 2022 09:36:55 +0000 Subject: [PATCH 77/81] remove unused --- frame/fast-unstake/src/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index a635359b79add..edc4099654084 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -25,7 +25,6 @@ use pallet_staking::{CurrentEra, IndividualExposure, RewardDestination}; use sp_runtime::{traits::BadOrigin, DispatchError, ModuleError}; use sp_staking::StakingInterface; -use sp_std::prelude::*; #[test] fn test_setup_works() { From 524da81afe84cc8efa535557a1d8fb1fc214e6ae Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Tue, 20 Sep 2022 12:28:51 +0000 Subject: [PATCH 78/81] ".git/.scripts/fmt.sh" 1 --- frame/fast-unstake/src/tests.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index edc4099654084..245e1a645959f 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -79,11 +79,7 @@ fn cannot_register_if_in_queue() { fn cannot_register_if_head() { ExtBuilder::default().build_and_execute(|| { // Insert some Head item for stash - Head::::put(UnstakeRequest { - stash: 1, - checked: bounded_vec![], - maybe_pool_id: None, - }); + Head::::put(UnstakeRequest { stash: 1, checked: bounded_vec![], maybe_pool_id: None }); // Controller attempts to regsiter assert_noop!( FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32)), @@ -141,11 +137,7 @@ fn cannot_deregister_already_head() { // Controller attempts to register, should fail assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); // Insert some Head item for stash. - Head::::put(UnstakeRequest { - stash: 1, - checked: bounded_vec![], - maybe_pool_id: None, - }); + Head::::put(UnstakeRequest { stash: 1, checked: bounded_vec![], maybe_pool_id: None }); // Controller attempts to deregister assert_noop!(FastUnstake::deregister(Origin::signed(2)), Error::::AlreadyHead); }); From 4d45cd009b60d937c8a2a3dfe485a39d667dfd3f Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 23 Sep 2022 09:30:36 +0100 Subject: [PATCH 79/81] make it all build again --- frame/fast-unstake/src/benchmarking.rs | 2 +- frame/fast-unstake/src/lib.rs | 2 +- frame/fast-unstake/src/mock.rs | 3 +- frame/fast-unstake/src/tests.rs | 86 +++++++++++++------------- 4 files changed, 47 insertions(+), 46 deletions(-) diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index a50a9f39069df..68a3da0d40af3 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -221,7 +221,7 @@ benchmarks! { control { let origin = ::ControlOrigin::successful_origin(); } - : _(origin, 128) + : _(origin, 128) verify {} impl_benchmark_test_suite!(Pallet, crate::mock::ExtBuilder::default().build(), crate::mock::Runtime) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 7e538352c11d1..51416808f48c8 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -126,7 +126,7 @@ pub mod pallet { type SlashPerEra: Get>; /// The origin that can control this pallet. - type ControlOrigin: frame_support::traits::EnsureOrigin; + type ControlOrigin: frame_support::traits::EnsureOrigin; /// The weight information of this pallet. type WeightInfo: WeightInfo; diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index c17e5f3f14fdf..b9cf16e18e8d1 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -49,7 +49,7 @@ impl frame_system::Config for Runtime { type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = AccountIndex; type BlockNumber = BlockNumber; type RuntimeCall = RuntimeCall; @@ -145,6 +145,7 @@ impl pallet_staking::Config for Runtime { type SessionInterface = (); type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = (); + type HistoryDepth = ConstU32<84>; type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = (); type ElectionProvider = MockElection; diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 245e1a645959f..4f4ccff58ebaa 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -41,7 +41,7 @@ fn test_setup_works() { fn register_works() { ExtBuilder::default().build_and_execute(|| { // Controller account registers for fast unstake. - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(1_u32))); // Ensure stash is in the queue. assert_ne!(Queue::::get(1), None); }); @@ -56,7 +56,7 @@ fn cannot_register_if_not_bonded() { } // Attempt to fast unstake. assert_noop!( - FastUnstake::register_fast_unstake(Origin::signed(1), Some(1_u32)), + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1), Some(1_u32)), Error::::NotController ); }); @@ -69,7 +69,7 @@ fn cannot_register_if_in_queue() { Queue::::insert(1, Some(1_u32)); // Cannot re-register, already in queue assert_noop!( - FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32)), + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(1_u32)), Error::::AlreadyQueued ); }); @@ -82,7 +82,7 @@ fn cannot_register_if_head() { Head::::put(UnstakeRequest { stash: 1, checked: bounded_vec![], maybe_pool_id: None }); // Controller attempts to regsiter assert_noop!( - FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32)), + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(1_u32)), Error::::AlreadyHead ); }); @@ -92,10 +92,10 @@ fn cannot_register_if_head() { fn cannot_register_if_has_unlocking_chunks() { ExtBuilder::default().build_and_execute(|| { // Start unbonding half of staked tokens - assert_ok!(Staking::unbond(Origin::signed(2), 50_u128)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(2), 50_u128)); // Cannot register for fast unstake with unlock chunks active assert_noop!( - FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32)), + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(1_u32)), Error::::NotFullyBonded ); }); @@ -105,9 +105,9 @@ fn cannot_register_if_has_unlocking_chunks() { fn deregister_works() { ExtBuilder::default().build_and_execute(|| { // Controller account registers for fast unstake. - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(1_u32))); // Controller then changes mind and deregisters. - assert_ok!(FastUnstake::deregister(Origin::signed(2))); + assert_ok!(FastUnstake::deregister(RuntimeOrigin::signed(2))); // Ensure stash no longer exists in the queue. assert_eq!(Queue::::get(1), None); }); @@ -117,9 +117,9 @@ fn deregister_works() { fn cannot_deregister_if_not_controller() { ExtBuilder::default().build_and_execute(|| { // Controller account registers for fast unstake. - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(1_u32))); // Stash tries to deregister. - assert_noop!(FastUnstake::deregister(Origin::signed(1)), Error::::NotController); + assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(1)), Error::::NotController); }); } @@ -127,7 +127,7 @@ fn cannot_deregister_if_not_controller() { fn cannot_deregister_if_not_queued() { ExtBuilder::default().build_and_execute(|| { // Controller tries to deregister without first registering - assert_noop!(FastUnstake::deregister(Origin::signed(2)), Error::::NotQueued); + assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(2)), Error::::NotQueued); }); } @@ -135,11 +135,11 @@ fn cannot_deregister_if_not_queued() { fn cannot_deregister_already_head() { ExtBuilder::default().build_and_execute(|| { // Controller attempts to register, should fail - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(1_u32))); // Insert some Head item for stash. Head::::put(UnstakeRequest { stash: 1, checked: bounded_vec![], maybe_pool_id: None }); // Controller attempts to deregister - assert_noop!(FastUnstake::deregister(Origin::signed(2)), Error::::AlreadyHead); + assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(2)), Error::::AlreadyHead); }); } @@ -147,7 +147,7 @@ fn cannot_deregister_already_head() { fn control_works() { ExtBuilder::default().build_and_execute(|| { // account with control (root) origin wants to only check 1 era per block. - assert_ok!(FastUnstake::control(Origin::root(), 1_u32)); + assert_ok!(FastUnstake::control(RuntimeOrigin::root(), 1_u32)); }); } @@ -155,7 +155,7 @@ fn control_works() { fn control_must_be_control_origin() { ExtBuilder::default().build_and_execute(|| { // account without control (root) origin wants to only check 1 era per block. - assert_noop!(FastUnstake::control(Origin::signed(1), 1_u32), BadOrigin); + assert_noop!(FastUnstake::control(RuntimeOrigin::signed(1), 1_u32), BadOrigin); }); } @@ -169,7 +169,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // set up Queue item - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(1))); assert_eq!(Queue::::get(1), Some(Some(1))); // call on_idle with no remaining weight @@ -189,7 +189,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // given - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(1))); assert_eq!(Queue::::get(1), Some(Some(1))); assert_eq!(Queue::::count(), 1); @@ -302,11 +302,11 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // given - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), None)); - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(4), None)); - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(6), None)); - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(8), None)); - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(10), None)); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), None)); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4), None)); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(6), None)); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(8), None)); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(10), None)); assert_eq!(Queue::::count(), 5); assert_eq!(Head::::get(), None); @@ -364,9 +364,9 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // register multi accounts for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(1))); assert_eq!(Queue::::get(1), Some(Some(1))); - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(4), Some(1))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4), Some(1))); assert_eq!(Queue::::get(3), Some(Some(1))); // assert 2 queue items are in Queue & None in Head to start with @@ -415,7 +415,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), None)); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), None)); assert_eq!(Queue::::get(1), Some(None)); // process on idle @@ -455,7 +455,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(0))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(0))); assert_eq!(Queue::::get(1), Some(Some(0))); // process on idle @@ -503,7 +503,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(1_u32))); assert_eq!(Queue::::get(1), Some(Some(1))); // process on idle @@ -545,7 +545,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(1_u32))); assert_eq!(Queue::::get(1), Some(Some(1))); // process on idle @@ -623,7 +623,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), None)); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), None)); assert_eq!(Queue::::get(1), Some(None)); next_block(true); @@ -700,7 +700,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), Some(1_u32))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), Some(1_u32))); // process 2 blocks next_block(true); @@ -804,15 +804,15 @@ mod on_idle { }); Balances::make_free_balance_be(&exposed, 100); assert_ok!(Staking::bond( - Origin::signed(exposed), + RuntimeOrigin::signed(exposed), exposed, 10, RewardDestination::Staked )); - assert_ok!(Staking::nominate(Origin::signed(exposed), vec![exposed])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(exposed), vec![exposed])); // register the exposed one. - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(exposed), None)); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(exposed), None)); // a few blocks later, we realize they are slashed next_block(true); @@ -864,15 +864,15 @@ mod on_idle { }); Balances::make_free_balance_be(&exposed, 100); assert_ok!(Staking::bond( - Origin::signed(exposed), + RuntimeOrigin::signed(exposed), exposed, 10, RewardDestination::Staked )); - assert_ok!(Staking::nominate(Origin::signed(exposed), vec![exposed])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(exposed), vec![exposed])); // register the exposed one. - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(exposed), None)); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(exposed), None)); // a few blocks later, we realize they are slashed next_block(true); @@ -905,8 +905,8 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // a validator switches role and register... - assert_ok!(Staking::nominate(Origin::signed(VALIDATOR_PREFIX), vec![VALIDATOR_PREFIX])); - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(VALIDATOR_PREFIX), None)); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(VALIDATOR_PREFIX), vec![VALIDATOR_PREFIX])); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(VALIDATOR_PREFIX), None)); // but they indeed are exposed! assert!(pallet_staking::ErasStakers::::contains_key( @@ -933,11 +933,11 @@ mod on_idle { // create a new validator that 100% not exposed. Balances::make_free_balance_be(&42, 100); - assert_ok!(Staking::bond(Origin::signed(42), 42, 10, RewardDestination::Staked)); - assert_ok!(Staking::validate(Origin::signed(42), Default::default())); + assert_ok!(Staking::bond(RuntimeOrigin::signed(42), 42, 10, RewardDestination::Staked)); + assert_ok!(Staking::validate(RuntimeOrigin::signed(42), Default::default())); // let them register: - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(42), None)); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(42), None)); // 2 block's enough to unstake them. next_block(true); @@ -984,7 +984,7 @@ mod signed_extension { ExtBuilder::default().build_and_execute(|| { // given: stash for 2 is 1. // when - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), None)); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), None)); // then // stash can't. @@ -1004,7 +1004,7 @@ mod signed_extension { ExtBuilder::default().build_and_execute(|| { // given: stash for 2 is 1. // when - assert_ok!(FastUnstake::register_fast_unstake(Origin::signed(2), None)); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2), None)); ErasToCheckPerBlock::::put(1); CurrentEra::::put(BondingDuration::get()); From f145c601f9eb2f3211b46a9fcdc86266f12c81e1 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 23 Sep 2022 09:32:50 +0100 Subject: [PATCH 80/81] fmt --- frame/fast-unstake/src/tests.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 4f4ccff58ebaa..a51c1acdf06eb 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -905,8 +905,14 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // a validator switches role and register... - assert_ok!(Staking::nominate(RuntimeOrigin::signed(VALIDATOR_PREFIX), vec![VALIDATOR_PREFIX])); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(VALIDATOR_PREFIX), None)); + assert_ok!(Staking::nominate( + RuntimeOrigin::signed(VALIDATOR_PREFIX), + vec![VALIDATOR_PREFIX] + )); + assert_ok!(FastUnstake::register_fast_unstake( + RuntimeOrigin::signed(VALIDATOR_PREFIX), + None + )); // but they indeed are exposed! assert!(pallet_staking::ErasStakers::::contains_key( From 91d31b46001782f33c447618fc7403ea7d52706e Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 23 Sep 2022 10:09:30 +0100 Subject: [PATCH 81/81] undo fishy change --- bin/node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 99238561432fc..8ed5f1c847f5e 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1424,7 +1424,7 @@ impl pallet_assets::Config for Runtime { type Balance = u128; type AssetId = u32; type Currency = Balances; - type ForceOrigin = EnsureSigned; + type ForceOrigin = EnsureRoot; type AssetDeposit = AssetDeposit; type AssetAccountDeposit = ConstU128; type MetadataDepositBase = MetadataDepositBase;