From 8e6be134f7248e6b0f6df258c5dae7c71413203d Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 10 Jan 2022 21:16:02 -0800 Subject: [PATCH 001/299] Save progress --- Cargo.lock | 12 +++ Cargo.toml | 1 + frame/pools/Cargo.toml | 34 ++++++++ frame/pools/src/lib.rs | 173 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 220 insertions(+) create mode 100644 frame/pools/Cargo.toml create mode 100644 frame/pools/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index e5192d2c8fe17..9fa379c6226bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5949,6 +5949,18 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-pools" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "log 0.4.14", + "parity-scale-codec", + "scale-info", + "sp-arithmetic", +] + [[package]] name = "pallet-preimage" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 93f7d42c8238c..b460013d0821e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,6 +101,7 @@ members = [ "frame/nicks", "frame/node-authorization", "frame/offences", + "frame/pools", "frame/preimage", "frame/proxy", "frame/randomness-collective-flip", diff --git a/frame/pools/Cargo.toml b/frame/pools/Cargo.toml new file mode 100644 index 0000000000000..de46382600a6e --- /dev/null +++ b/frame/pools/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "pallet-pools" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pools pallet" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# parity +codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "1.0", default-features = false, features = ["derive"] } + +# FRAME +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-arithmetic = { version = "4.0.0", default-features = false, path = "../../primitives/arithmetic" } + +# third party +log = { version = "0.4.14", default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "sp-arithmetic/std", +] \ No newline at end of file diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs new file mode 100644 index 0000000000000..f24fc25e18774 --- /dev/null +++ b/frame/pools/src/lib.rs @@ -0,0 +1,173 @@ +//! Delegation pools for nominating in `pallet-staking`. + +#![cfg_attr(not(feature = "std"), no_std)] + +use scale_info::TypeInfo; +use sp_arithmetic::{FixedU128, FixedPointNumber}; +use frame_support::{ensure, pallet_prelude::*, traits::{Currency, ExistenceRequirement}}; +use sp_arithmetic::traits::Saturating; + + +pub use pallet::*; +pub(crate) const LOG_TARGET: &'static str = "runtime::pools"; + +// 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)* + ) + }; +} + +type PoolId = u32; +type Shares = u128; +type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; + +pub trait NominationProviderTrait {} + +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] +pub struct Delegator { + pool: PoolId, + shares: Shares, +} + +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct Pool { + shares: Shares, + balance: BalanceOf, + account_id: T::AccountId, +} + +impl Pool { +// /// Number of shares per balance that can be exchanged for `Balance`. +// /// +// /// Typically used upon entering. + fn shares_per_balance(&self) -> FixedU128 { + let balance_u128: u128 = self.balance.try_into().unwrap_or_default(); + let shares_u128: u128 = self.shares.try_into().unwrap_or_default(); + FixedU128::saturating_from_rational(shares_u128, balance_u128) + } + + fn shares_to_issue(&self, new_funds: BalanceOf) -> Shares { + let new_funds_u128: u128 = new_funds.try_into().unwrap_or_default(); + self.shares_per_balance().saturating_mul_int(new_funds_u128 + ) + } +} + +// #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] +// #[codec(mel_bound(T: Config))] +// struct SubPools { +// shares: Shares, +// balance: Balance, +// account_id: T::AccountId, +// } + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_system::{ensure_signed, pallet_prelude::*}; + + #[pallet::pallet] + #[pallet::generate_store(pub(crate) trait Store)] + #[pallet::generate_storage_info] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + // type Event: From> + IsType<::Event>; + + /// Weight information for extrinsics in this pallet. + // type WeightInfo: weights::WeightInfo; + + /// The interface for nominating. + type NominationProvider: NominationProviderTrait; + + /// The nominating balance. + type Currency: Currency; + + // TODO use this for conversion + // type BalanceToU128: Convert, U128>; + + // MaxPools + } + + /// Bonded pools. + #[pallet::storage] + pub(crate) type PrimaryPools = CountedStorageMap<_, Twox64Concat, PoolId, Pool>; + + /// Active delegators. + #[pallet::storage] + pub(crate) type Delegators = CountedStorageMap<_, Twox64Concat, T::AccountId, Delegator>; + + // /// Groups of unbonding pools. Each group of unbonding pools belongs to a primary pool, + // /// hence the name sub-pools. + // #[pallet::storage] + // type SubPools = CountedStorageMap<_, PoolId, SubPools, _>; + + // #[pallet::event] + // #[pallet::generate_deposit(pub(crate) fn deposit_event)] + // pub enum Event {} + + #[pallet::error] + #[cfg_attr(test, derive(PartialEq))] + pub enum Error { + /// The given pool id does not exist. + PoolNotFound, + /// The account is already delegating in another pool. An account may only belong to one + /// pool at a time. + AccountBelongsToOtherPool, + } + + #[pallet::call] + impl Pallet { + #[pallet::weight(666)] + pub fn join(origin: OriginFor, amount: BalanceOf, target: PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); + + // Ensure that the `target` pool exists + let mut primary_pool = PrimaryPools::::get(target).ok_or(Error::::PoolNotFound)?; + + // Ensure that the delegator does not back o + + let old_free_balance = T::Currency::free_balance(&primary_pool.account_id); + + // Ensure that `who` has `amount` transferable and do the actual transfer + T::Currency::transfer( + &who, + &primary_pool.account_id, + amount, + ExistenceRequirement::KeepAlive, + )?; + + // this should now include the transferred balance; + primary_pool.balance = T::Currency::free_balance(&primary_pool.account_id); + + let actual_amount_to_bond = primary_pool.balance.saturating_sub(old_free_balance); + + let new_shares = primary_pool.shares_to_issue(amount); + primary_pool.shares = primary_pool.shares.saturating_add(new_shares); + // TODO: maybe some other checks to ensure that the pool is in an ok state + + // calculate the amount of shares to issue + + // Bond or BondExtra, depending on the pools balacn + + // write the pool to storage + + Ok(()) + } + + // pub fn force_create_pool(origin: ) -> DispatchResult { + + // } + } +} From 88e23159fa4b621b15beb2ab9fb04f601c5c032e Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 11 Jan 2022 15:33:24 -0800 Subject: [PATCH 002/299] Join pool MvP --- Cargo.lock | 1 + frame/pools/Cargo.toml | 1 + frame/pools/src/lib.rs | 241 ++++++++++++++++++++++++++++++++--------- 3 files changed, 189 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9fa379c6226bf..7aef4c20d6c41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5959,6 +5959,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-arithmetic", + "sp-runtime", ] [[package]] diff --git a/frame/pools/Cargo.toml b/frame/pools/Cargo.toml index de46382600a6e..3c8959113bdf6 100644 --- a/frame/pools/Cargo.toml +++ b/frame/pools/Cargo.toml @@ -20,6 +20,7 @@ scale-info = { version = "1.0", default-features = false, features = ["derive"] frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-arithmetic = { version = "4.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-runtime = { version = "4.0.0", default-features = false, path = "../../primitives/runtime" } # third party log = { version = "0.4.14", default-features = false } diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index f24fc25e18774..a689a6b996713 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -2,11 +2,14 @@ #![cfg_attr(not(feature = "std"), no_std)] +use frame_support::{ + ensure, + pallet_prelude::*, + traits::{Currency, ExistenceRequirement}, +}; use scale_info::TypeInfo; -use sp_arithmetic::{FixedU128, FixedPointNumber}; -use frame_support::{ensure, pallet_prelude::*, traits::{Currency, ExistenceRequirement}}; -use sp_arithmetic::traits::Saturating; - +use sp_arithmetic::{traits::Saturating, FixedPointNumber, FixedU128}; +use sp_runtime::traits::{AtLeast32BitUnsigned, Convert, One, Zero}; pub use pallet::*; pub(crate) const LOG_TARGET: &'static str = "runtime::pools"; @@ -23,41 +26,73 @@ macro_rules! log { } type PoolId = u32; -type Shares = u128; -type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; +type SharesOf = BalanceOf; + +pub trait NominationProviderTrait { + /// The minimum amount necessary to bond to be a nominator. This does not necessarily mean the + /// nomination will be counted in an election, but instead just enough to be stored as a + /// nominator (e.g. in the bags-list of polkadot) + fn minimum_bond() -> Balance; + + /// The current era for the elections system + fn current_era() -> EraIndex; -pub trait NominationProviderTrait {} + /// Wether or not the elections system has an ongoing election. If there is an ongoing election + /// it is assumed that any new pool joiner's funds will not start earning rewards until the + /// following era. + fn is_ongoing_election() -> bool; + + fn bond_extra(controller: &AccountId, extra: Balance) -> DispatchResult; +} #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] -pub struct Delegator { +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct Delegator { pool: PoolId, - shares: Shares, + shares: SharesOf, + last_paid_out_era: T::EraIndex, + + /// The reward pools total payout ever the last time this delegator claimed a payout. + total_payout_ever: BalanceOf, // Probably needs to be some typ eof BigUInt } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct Pool { - shares: Shares, - balance: BalanceOf, + shares: SharesOf, + // The _Stash_ AND _Controller_ account for the pool. account_id: T::AccountId, } impl Pool { -// /// Number of shares per balance that can be exchanged for `Balance`. -// /// -// /// Typically used upon entering. - fn shares_per_balance(&self) -> FixedU128 { - let balance_u128: u128 = self.balance.try_into().unwrap_or_default(); - let shares_u128: u128 = self.shares.try_into().unwrap_or_default(); - FixedU128::saturating_from_rational(shares_u128, balance_u128) + fn shares_to_issue(&self, new_funds: BalanceOf, pool_free: BalanceOf) -> SharesOf { + let shares_per_balance = { + let balance = T::BalanceToU128::convert(pool_free); + let shares = T::BalanceToU128::convert(self.shares); + FixedU128::saturating_from_rational(shares, balance) + }; + let new_funds = T::BalanceToU128::convert(new_funds); + + T::U128ToBalance::convert(shares_per_balance.saturating_mul_int(new_funds)) } +} - fn shares_to_issue(&self, new_funds: BalanceOf) -> Shares { - let new_funds_u128: u128 = new_funds.try_into().unwrap_or_default(); - self.shares_per_balance().saturating_mul_int(new_funds_u128 - ) - } +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct RewardPool { + // TODO look into using the BigUInt + /// The balance of this reward pool after the last claimed payout. + balance: BalanceOf, + /// The shares of this reward pool after the last claimed payout + shares: BalanceOf, + /// The total earnings ever of this reward pool after the last claimed payout. I.E. the sum of + /// all incoming balance. + total_earnings: BalanceOf, } // #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] @@ -81,93 +116,191 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - // type Event: From> + IsType<::Event>; + type Event: From> + IsType<::Event>; /// Weight information for extrinsics in this pallet. // type WeightInfo: weights::WeightInfo; - /// The interface for nominating. - type NominationProvider: NominationProviderTrait; - /// The nominating balance. type Currency: Currency; - // TODO use this for conversion - // type BalanceToU128: Convert, U128>; + // Infallible method for converting `Currency::Balance` to `u128`. + type BalanceToU128: Convert, u128>; + + // Infallible method for converting `u128` to `Currency::Balance`. + type U128ToBalance: Convert>; + + /// The type for unique era indexes. Likely comes from what implements `NominationProvider`. + type EraIndex: Member + + Parameter + + AtLeast32BitUnsigned + + Default + + Copy + + MaybeSerializeDeserialize + + MaxEncodedLen + + TypeInfo; + + /// The interface for nominating. + type NominationProvider: NominationProviderTrait< + BalanceOf, + Self::AccountId, + Self::EraIndex, + >; // MaxPools } + /// Active delegators. + #[pallet::storage] + pub(crate) type Delegators = + CountedStorageMap<_, Twox64Concat, T::AccountId, Delegator>; + /// Bonded pools. #[pallet::storage] pub(crate) type PrimaryPools = CountedStorageMap<_, Twox64Concat, PoolId, Pool>; - /// Active delegators. + /// Reward pools. This is where there rewards for each pool accumulate. When a delegators payout + /// is claimed, the balance comes out fo the reward pool. #[pallet::storage] - pub(crate) type Delegators = CountedStorageMap<_, Twox64Concat, T::AccountId, Delegator>; + pub(crate) type RewardPools = + CountedStorageMap<_, Twox64Concat, PoolId, RewardPool>; // /// Groups of unbonding pools. Each group of unbonding pools belongs to a primary pool, // /// hence the name sub-pools. // #[pallet::storage] // type SubPools = CountedStorageMap<_, PoolId, SubPools, _>; - // #[pallet::event] - // #[pallet::generate_deposit(pub(crate) fn deposit_event)] - // pub enum Event {} + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + Joined { delegator: T::AccountId, pool: PoolId, bonded: BalanceOf }, + } #[pallet::error] #[cfg_attr(test, derive(PartialEq))] pub enum Error { /// The given pool id does not exist. PoolNotFound, + /// The given account is not a delegator. + DelegatorNotFound, /// The account is already delegating in another pool. An account may only belong to one /// pool at a time. AccountBelongsToOtherPool, + /// The pool has insufficient balance to bond as a nominator. + InsufficientBond, } #[pallet::call] impl Pallet { + /// Join a pre-existing pool. Note that an account can only be a member of a single pool. #[pallet::weight(666)] pub fn join(origin: OriginFor, amount: BalanceOf, target: PoolId) -> DispatchResult { let who = ensure_signed(origin)?; - + // if a delegator already exists that means they already belong to a pool ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); // Ensure that the `target` pool exists - let mut primary_pool = PrimaryPools::::get(target).ok_or(Error::::PoolNotFound)?; - - // Ensure that the delegator does not back o - + let mut primary_pool = + PrimaryPools::::get(target).ok_or(Error::::PoolNotFound)?; + // And that `amount` will meet the minimum bond let old_free_balance = T::Currency::free_balance(&primary_pool.account_id); - - // Ensure that `who` has `amount` transferable and do the actual transfer + ensure!( + old_free_balance.saturating_add(amount) >= T::NominationProvider::minimum_bond(), + Error::::InsufficientBond + ); + + // Transfer the funds to be bonded from `who` to the pools account so the pool can then + // go bond them. + // Note importantly that we can't error after this transfer goes through. + // TODO I assume this does proper keep alive checks etc but need to double check T::Currency::transfer( &who, &primary_pool.account_id, amount, ExistenceRequirement::KeepAlive, )?; + // this should now include the transferred balance + let new_free_balance = T::Currency::free_balance(&primary_pool.account_id); + // we get the exact amount we can bond extra + let exact_amount_to_bond = new_free_balance.saturating_sub(old_free_balance); - // this should now include the transferred balance; - primary_pool.balance = T::Currency::free_balance(&primary_pool.account_id); - - let actual_amount_to_bond = primary_pool.balance.saturating_sub(old_free_balance); - - let new_shares = primary_pool.shares_to_issue(amount); + // issue the new shares + let new_shares = primary_pool.shares_to_issue(exact_amount_to_bond, old_free_balance); primary_pool.shares = primary_pool.shares.saturating_add(new_shares); - // TODO: maybe some other checks to ensure that the pool is in an ok state + let delegator = { + // Determine the last paid out era. Note that in the reward calculations we will + // start attributing rewards to this delegator in `last_paid_out_era + 1`. + let last_paid_out_era = if T::NominationProvider::is_ongoing_election() { + // If there is an ongoing election the new funds will start to help generating + // rewards in current_era + 2. + T::NominationProvider::current_era().saturating_add(One::one()) + } else { + // If there is no ongoing election, the new funds will start help generating + // generating rewards in current_era + 1. + T::NominationProvider::current_era() + }; + + Delegator:: { + pool: target, + shares: new_shares, + last_paid_out_era, + total_payout_ever: Zero::zero(), + } + }; + + // Do bond extra + T::NominationProvider::bond_extra(&primary_pool.account_id, exact_amount_to_bond)?; + + // Write the pool and delegator to storage + Delegators::insert(who.clone(), delegator); + PrimaryPools::insert(target, primary_pool); + + // And finally emit an event to confirm the exact amount bonded + Self::deposit_event(Event::::Joined { + delegator: who, + pool: target, + bonded: exact_amount_to_bond, + }); - // calculate the amount of shares to issue + Ok(()) + } - // Bond or BondExtra, depending on the pools balacn + // fn unbond() - // write the pool to storage + // /// Claim a payout for a delegator can use this to claim their payout based on the + // rewards /// that the pool has accumulated since their last claimed payout (OR since + // joining if this /// is there for). The payout will go to the delegators account. + // /// + // /// This extrinsic is permisionless in the sense that any account can call it for any + // /// delegator in the system. + // #[pallet::weight(666)] + // pub fn claim_payout_other(origin: OriginFor, delegator: T::AccountId) -> + // DispatchResult { ensure_signed(origin)?; - Ok(()) - } + // let delegator = Delegators::::get(&delegator).ok_or(Error::::DelegatorNotFound)?; - // pub fn force_create_pool(origin: ) -> DispatchResult { + // let reward_pool = RewardPools::::get(&delegator.pool).unwrap_or_else(|| { + // // The reward pool does not yet exist; this must be the first claimed payout for + // // the pool, so we must make the reward pool + // RewardPool { balance: Zero::zero(), shares: Zero::zero(), total_earnings: Zero::zero() } + // }); + // Ok(()) // } } } + +// impl Pallet { +// do_create_pool( +// creator: T::AccountId, +// targets: Vec, +// amount: BalanceOf +// ) -> DispatchResult { +// Create Stash/Controller account based on parent block hash, block number, and extrinsic index +// Create Reward Pool account based on Stash/Controller account + +// Bond with `amount`, ensuring that it is over the minimum bond (by min) + +// +// } +// } From 227db4275cbfec51c6bf681e68e7106f11c6a747 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 11 Jan 2022 18:01:32 -0800 Subject: [PATCH 003/299] claim payout mvp --- frame/pools/src/lib.rs | 182 ++++++++++++++++++++++++++++++----------- 1 file changed, 133 insertions(+), 49 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index a689a6b996713..7f351983e2110 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -1,4 +1,11 @@ //! Delegation pools for nominating in `pallet-staking`. +//! +//! Each pool is represented by: (the actively staked funds), a rewards pool (the +//! rewards earned by the actively staked funds) and a group of unbonding pools (pools ) +//! +//! * primary pool: This pool represents the actively staked funds ... +//! * rewards pool: The rewards earned by actively staked funds. Delegator can withdraw rewards once +//! they #![cfg_attr(not(feature = "std"), no_std)] @@ -8,8 +15,8 @@ use frame_support::{ traits::{Currency, ExistenceRequirement}, }; use scale_info::TypeInfo; -use sp_arithmetic::{traits::Saturating, FixedPointNumber, FixedU128}; -use sp_runtime::traits::{AtLeast32BitUnsigned, Convert, One, Zero}; +use sp_arithmetic::{FixedPointNumber, FixedU128}; +use sp_runtime::traits::{AtLeast32BitUnsigned, Convert, One, Saturating, Zero}; pub use pallet::*; pub(crate) const LOG_TARGET: &'static str = "runtime::pools"; @@ -53,18 +60,20 @@ pub trait NominationProviderTrait { pub struct Delegator { pool: PoolId, shares: SharesOf, - last_paid_out_era: T::EraIndex, - /// The reward pools total payout ever the last time this delegator claimed a payout. - total_payout_ever: BalanceOf, // Probably needs to be some typ eof BigUInt + /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. + /// Assumiing no massive burning events, we expect this value to always be below total + /// issuance. This value lines up with the `RewardPool.total_earnings` after a delegator claims + /// a payout. TODO ^ double check the above is an OK assumption + reward_pool_total_earnings: BalanceOf, } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct Pool { - shares: SharesOf, - // The _Stash_ AND _Controller_ account for the pool. + shares: SharesOf, // Probably needs to be some type of BigUInt + // The _Stash_ and _Controller_ account for the pool. account_id: T::AccountId, } @@ -90,9 +99,11 @@ pub struct RewardPool { balance: BalanceOf, /// The shares of this reward pool after the last claimed payout shares: BalanceOf, - /// The total earnings ever of this reward pool after the last claimed payout. I.E. the sum of - /// all incoming balance. + /// The total earnings _ever_ of this reward pool after the last claimed payout. I.E. the sum + /// of all incoming balance. total_earnings: BalanceOf, + /// The reward destination for the pool. + account_id: T::AccountId, } // #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] @@ -173,16 +184,21 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { + // TODO: these operations are per delegator - so these events could become decently noisy + // if things scale a lot - consider not including these. Joined { delegator: T::AccountId, pool: PoolId, bonded: BalanceOf }, + Payout { delegator: T::AccountId, pool: PoolId, payout: BalanceOf }, } #[pallet::error] #[cfg_attr(test, derive(PartialEq))] pub enum Error { - /// The given pool id does not exist. + /// The given (primary) pool id does not exist. PoolNotFound, /// The given account is not a delegator. DelegatorNotFound, + // The given reward pool does not exist. In all cases this is a system logic error. + RewardPoolNotFound, /// The account is already delegating in another pool. An account may only belong to one /// pool at a time. AccountBelongsToOtherPool, @@ -227,25 +243,12 @@ pub mod pallet { // issue the new shares let new_shares = primary_pool.shares_to_issue(exact_amount_to_bond, old_free_balance); primary_pool.shares = primary_pool.shares.saturating_add(new_shares); - let delegator = { - // Determine the last paid out era. Note that in the reward calculations we will - // start attributing rewards to this delegator in `last_paid_out_era + 1`. - let last_paid_out_era = if T::NominationProvider::is_ongoing_election() { - // If there is an ongoing election the new funds will start to help generating - // rewards in current_era + 2. - T::NominationProvider::current_era().saturating_add(One::one()) - } else { - // If there is no ongoing election, the new funds will start help generating - // generating rewards in current_era + 1. - T::NominationProvider::current_era() - }; - - Delegator:: { - pool: target, - shares: new_shares, - last_paid_out_era, - total_payout_ever: Zero::zero(), - } + let delegator = Delegator:: { + pool: target, + shares: new_shares, + // TODO this likely needs to be the reward pools total earnings at this block + // - go and double check + reward_pool_total_earnings: Zero::zero(), }; // Do bond extra @@ -267,26 +270,102 @@ pub mod pallet { // fn unbond() - // /// Claim a payout for a delegator can use this to claim their payout based on the - // rewards /// that the pool has accumulated since their last claimed payout (OR since - // joining if this /// is there for). The payout will go to the delegators account. - // /// - // /// This extrinsic is permisionless in the sense that any account can call it for any - // /// delegator in the system. - // #[pallet::weight(666)] - // pub fn claim_payout_other(origin: OriginFor, delegator: T::AccountId) -> - // DispatchResult { ensure_signed(origin)?; - - // let delegator = Delegators::::get(&delegator).ok_or(Error::::DelegatorNotFound)?; - - // let reward_pool = RewardPools::::get(&delegator.pool).unwrap_or_else(|| { - // // The reward pool does not yet exist; this must be the first claimed payout for - // // the pool, so we must make the reward pool - // RewardPool { balance: Zero::zero(), shares: Zero::zero(), total_earnings: Zero::zero() } - // }); - - // Ok(()) - // } + /// Claim a payout for a delegator can use this to claim their payout based on the + /// rewards /// that the pool has accumulated since their last claimed payout (OR since + /// + /// joining if this /// is there for). The payout will go to the delegators account. + /// + /// This extrinsic is permisionless in the sense that any account can call it for any + /// delegator in the system. + #[pallet::weight(666)] + pub fn claim_payout_other(origin: OriginFor, account: T::AccountId) -> DispatchResult { + ensure_signed(origin)?; + + // READ THINGS FROM STORAGE + let mut delegator = + Delegators::::get(&account).ok_or(Error::::DelegatorNotFound)?; + let primary_pool = PrimaryPools::::get(&delegator.pool).ok_or_else(|| { + log!(error, "A primary pool could not be found, this is a system logic error."); + debug_assert!( + false, + "A primary pool could not be found, this is a system logic error." + ); + Error::::PoolNotFound + })?; + let mut reward_pool = RewardPools::::get(&delegator.pool).ok_or_else(|| { + log!(error, "A reward pool could not be found, this is a system logic error."); + debug_assert!( + false, + "A reward pool could not be found, this is a system logic error." + ); + Error::::RewardPoolNotFound + })?; + let current_balance = T::Currency::free_balance(&reward_pool.account_id); + + // DO MATHS TO CALCULATE PAYOUT + + // The earnings since the last claimed payout + let new_earnings = current_balance.saturating_sub(reward_pool.balance); + + // The lifetime earnings of the of the reward pool + let new_total_earnings = new_earnings.saturating_add(reward_pool.total_earnings); + + // The new shares that will be added to the pool. For every unit of balance that has + // been earned by the reward pool, we inflate the reward pool shares by + // `primary_pool.total_shares`. In effect this allows each, single unit of balance (e.g. + // plank) to be divvied up pro-rata among delegators based on shares. + // TODO this needs to be some sort of BigUInt arithmetic + let new_shares = primary_pool.shares.saturating_mul(new_earnings); + + // The shares of the reward pool after taking into account the new earnings + let current_shares = reward_pool.shares.saturating_add(new_shares); + + // The rewards pool's earnings since the last time this delegator claimed a payout + let new_earnings_since_last_claim = + new_total_earnings.saturating_sub(delegator.reward_pool_total_earnings); + // The shares of the reward pool that belong to the delegator. + let delegator_virtual_shares = + delegator.shares.saturating_mul(new_earnings_since_last_claim); + + let delegator_payout = { + let delegator_ratio_of_shares = FixedU128::saturating_from_rational( + T::BalanceToU128::convert(delegator_virtual_shares), + T::BalanceToU128::convert(current_shares), + ); + + let payout = delegator_ratio_of_shares + .saturating_mul_int(T::BalanceToU128::convert(current_balance)); + T::U128ToBalance::convert(payout) + }; + + // TRANSFER PAYOUT TO DELEGATOR + T::Currency::transfer( + &reward_pool.account_id, + &account, + delegator_payout, + // TODO double check we are ok with dusting the account - If their is a very high + // ED this could lead to a non-negligible loss of rewards + ExistenceRequirement::AllowDeath, // Dust may be lost here + )?; + + Self::deposit_event(Event::::Payout { + delegator: account.clone(), + pool: delegator.pool, + payout: delegator_payout, + }); + + // RECORD RELEVANT UPDATES + delegator.reward_pool_total_earnings = new_total_earnings; + reward_pool.shares = current_shares.saturating_sub(delegator_virtual_shares); + reward_pool.balance = current_balance; + reward_pool.total_earnings = new_total_earnings; + + // WRITE UPDATED DELEGATOR AND REWARD POOL TO STORAGE + RewardPools::insert(delegator.pool, reward_pool); + Delegators::insert(account.clone(), delegator); + + Ok(()) + } } } @@ -298,8 +377,13 @@ pub mod pallet { // ) -> DispatchResult { // Create Stash/Controller account based on parent block hash, block number, and extrinsic index // Create Reward Pool account based on Stash/Controller account +// Move `amount` to the stash / controller account +// Read in `bondable` - the free balance that we can bond after any neccesary reserv etc // Bond with `amount`, ensuring that it is over the minimum bond (by min) +// (might has need to ensure number of targets etc is valid) + +// Generate a pool id (look at how assets IDs are generated for inspiration) // // } From ac8d1183cd87b177083171a8093aaef3036b8de2 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 11 Jan 2022 18:17:26 -0800 Subject: [PATCH 004/299] WIP introduce BigUint --- frame/pools/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 7f351983e2110..fd651f0f86a70 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -15,7 +15,7 @@ use frame_support::{ traits::{Currency, ExistenceRequirement}, }; use scale_info::TypeInfo; -use sp_arithmetic::{FixedPointNumber, FixedU128}; +use sp_arithmetic::{FixedPointNumber, FixedU128, biguint::BigUint}; use sp_runtime::traits::{AtLeast32BitUnsigned, Convert, One, Saturating, Zero}; pub use pallet::*; @@ -98,7 +98,7 @@ pub struct RewardPool { /// The balance of this reward pool after the last claimed payout. balance: BalanceOf, /// The shares of this reward pool after the last claimed payout - shares: BalanceOf, + shares: BigUint, /// The total earnings _ever_ of this reward pool after the last claimed payout. I.E. the sum /// of all incoming balance. total_earnings: BalanceOf, @@ -315,10 +315,10 @@ pub mod pallet { // `primary_pool.total_shares`. In effect this allows each, single unit of balance (e.g. // plank) to be divvied up pro-rata among delegators based on shares. // TODO this needs to be some sort of BigUInt arithmetic - let new_shares = primary_pool.shares.saturating_mul(new_earnings); + let new_shares = primary_pool.shares.mul(new_earnings.into()); // The shares of the reward pool after taking into account the new earnings - let current_shares = reward_pool.shares.saturating_add(new_shares); + let current_shares = reward_pool.shares.add(new_shares); // The rewards pool's earnings since the last time this delegator claimed a payout let new_earnings_since_last_claim = From 9a9daf0481a7f2193db98f44da1ca8afbe83e4b4 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 12 Jan 2022 10:10:38 -0800 Subject: [PATCH 005/299] WIP --- frame/pools/src/lib.rs | 67 +++++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index fd651f0f86a70..d553c7a071a77 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -9,13 +9,15 @@ #![cfg_attr(not(feature = "std"), no_std)] +use std::collections::BTreeMap; + use frame_support::{ ensure, pallet_prelude::*, traits::{Currency, ExistenceRequirement}, }; use scale_info::TypeInfo; -use sp_arithmetic::{FixedPointNumber, FixedU128, biguint::BigUint}; +use sp_arithmetic::{FixedPointNumber, FixedU128}; use sp_runtime::traits::{AtLeast32BitUnsigned, Convert, One, Saturating, Zero}; pub use pallet::*; @@ -59,6 +61,7 @@ pub trait NominationProviderTrait { #[scale_info(skip_type_params(T))] pub struct Delegator { pool: PoolId, + /// The quantity of shares this delegator has in the p shares: SharesOf, /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. @@ -66,6 +69,8 @@ pub struct Delegator { /// issuance. This value lines up with the `RewardPool.total_earnings` after a delegator claims /// a payout. TODO ^ double check the above is an OK assumption reward_pool_total_earnings: BalanceOf, + /// The era this delegator started unbonding at. + unbonding_era: Option, } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] @@ -80,6 +85,7 @@ pub struct Pool { impl Pool { fn shares_to_issue(&self, new_funds: BalanceOf, pool_free: BalanceOf) -> SharesOf { let shares_per_balance = { + // TODO if balance.is_zero() || shares.is_zero() let balance = T::BalanceToU128::convert(pool_free); let shares = T::BalanceToU128::convert(self.shares); FixedU128::saturating_from_rational(shares, balance) @@ -98,7 +104,7 @@ pub struct RewardPool { /// The balance of this reward pool after the last claimed payout. balance: BalanceOf, /// The shares of this reward pool after the last claimed payout - shares: BigUint, + shares: BalanceOf, // TODO maybe MaxEncodedLen or something /// The total earnings _ever_ of this reward pool after the last claimed payout. I.E. the sum /// of all incoming balance. total_earnings: BalanceOf, @@ -106,13 +112,18 @@ pub struct RewardPool { account_id: T::AccountId, } -// #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] -// #[codec(mel_bound(T: Config))] -// struct SubPools { -// shares: Shares, -// balance: Balance, -// account_id: T::AccountId, -// } +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] +struct UnbondPool { + +} + + +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] +#[codec(mel_bound(T: Config))] +struct SubPools { + unbonding: UnbondPool, + unbonding: BTreeMap +} #[frame_support::pallet] pub mod pallet { @@ -158,6 +169,10 @@ pub mod pallet { Self::EraIndex, >; + /// The maximum amount of eras an unbonding pool can exist prior to being merged with the + /// "average" (TODO need better terminology) unbonding pool. + const MAX_UNBOND_POOL_LIFE: u32; + // MaxPools } @@ -204,6 +219,8 @@ pub mod pallet { AccountBelongsToOtherPool, /// The pool has insufficient balance to bond as a nominator. InsufficientBond, + /// The delegator is already unbonding. + AlreadyUnbonding, } #[pallet::call] @@ -249,6 +266,7 @@ pub mod pallet { // TODO this likely needs to be the reward pools total earnings at this block // - go and double check reward_pool_total_earnings: Zero::zero(), + unbonding_era: None, }; // Do bond extra @@ -268,8 +286,6 @@ pub mod pallet { Ok(()) } - // fn unbond() - /// Claim a payout for a delegator can use this to claim their payout based on the /// rewards /// that the pool has accumulated since their last claimed payout (OR since /// @@ -284,6 +300,10 @@ pub mod pallet { // READ THINGS FROM STORAGE let mut delegator = Delegators::::get(&account).ok_or(Error::::DelegatorNotFound)?; + + // If the delegator is unbonding they cannot claim rewards. Note that when the delagator + // goes to unbond, the unbond function should claim rewards for the final time. + ensure!(delegator.unbonding_era.is_none(), Error::::AlreadyUnbonding); let primary_pool = PrimaryPools::::get(&delegator.pool).ok_or_else(|| { log!(error, "A primary pool could not be found, this is a system logic error."); debug_assert!( @@ -315,10 +335,10 @@ pub mod pallet { // `primary_pool.total_shares`. In effect this allows each, single unit of balance (e.g. // plank) to be divvied up pro-rata among delegators based on shares. // TODO this needs to be some sort of BigUInt arithmetic - let new_shares = primary_pool.shares.mul(new_earnings.into()); + let new_shares = primary_pool.shares.saturating_mul(new_earnings); // The shares of the reward pool after taking into account the new earnings - let current_shares = reward_pool.shares.add(new_shares); + let current_shares = reward_pool.shares.saturating_add(new_shares); // The rewards pool's earnings since the last time this delegator claimed a payout let new_earnings_since_last_claim = @@ -366,7 +386,26 @@ pub mod pallet { Ok(()) } - } + + /// Unbond _all_ funds. + #[pallet::weight(666)] + pub fn unbond(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let mut delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + + // Unbonding is all or nothing and a delegator can only belong to 1 pool. + ensure!(delegator.unbonding_era.is_none(), Error::::AlreadyUnbonding); + + let primary_pool = PrimaryPools::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; + let sub_pools = SubPools::::get(delegator.pool).ok_or_else(|| { + // make sub pool + })?; + + + // TODO claim rewards (will need to refactor the rewards function) + Ok(()) + } + } } // impl Pallet { From 4e8ee97724b8faaaae56802da3a7848404750e6f Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 13 Jan 2022 16:53:06 -0800 Subject: [PATCH 006/299] Unbond MVP --- Cargo.lock | 1 + frame/pools/Cargo.toml | 1 + frame/pools/src/lib.rs | 237 ++++++++++++++++++++++++++------ frame/staking/src/pallet/mod.rs | 49 ++++--- 4 files changed, 226 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7aef4c20d6c41..4c6a00f529ed0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5960,6 +5960,7 @@ dependencies = [ "scale-info", "sp-arithmetic", "sp-runtime", + "sp-std", ] [[package]] diff --git a/frame/pools/Cargo.toml b/frame/pools/Cargo.toml index 3c8959113bdf6..2e373d8c9b45b 100644 --- a/frame/pools/Cargo.toml +++ b/frame/pools/Cargo.toml @@ -21,6 +21,7 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../su frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-arithmetic = { version = "4.0.0", default-features = false, path = "../../primitives/arithmetic" } sp-runtime = { version = "4.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } # third party log = { version = "0.4.14", default-features = false } diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index d553c7a071a77..a86fc36552c30 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -9,16 +9,15 @@ #![cfg_attr(not(feature = "std"), no_std)] -use std::collections::BTreeMap; - use frame_support::{ ensure, pallet_prelude::*, - traits::{Currency, ExistenceRequirement}, + storage::bounded_btree_map::BoundedBTreeMap, + traits::{Currency, ExistenceRequirement, Get}, }; use scale_info::TypeInfo; use sp_arithmetic::{FixedPointNumber, FixedU128}; -use sp_runtime::traits::{AtLeast32BitUnsigned, Convert, One, Saturating, Zero}; +use sp_runtime::traits::{AtLeast32BitUnsigned, Convert, Saturating, Zero}; pub use pallet::*; pub(crate) const LOG_TARGET: &'static str = "runtime::pools"; @@ -38,6 +37,7 @@ type PoolId = u32; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type SharesOf = BalanceOf; +type SubPoolsWithEra = BoundedBTreeMap<::EraIndex, UnbondPool, MaxUnbonding>; pub trait NominationProviderTrait { /// The minimum amount necessary to bond to be a nominator. This does not necessarily mean the @@ -53,7 +53,12 @@ pub trait NominationProviderTrait { /// following era. fn is_ongoing_election() -> bool; + /// Balance `controller` has bonded for nominating. + fn bonded_balance(controller: &AccountId) -> Balance; + fn bond_extra(controller: &AccountId, extra: Balance) -> DispatchResult; + + fn unbond(controller: &AccountId, value: Balance) -> DispatchResult; } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] @@ -63,7 +68,6 @@ pub struct Delegator { pool: PoolId, /// The quantity of shares this delegator has in the p shares: SharesOf, - /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. /// Assumiing no massive burning events, we expect this value to always be below total /// issuance. This value lines up with the `RewardPool.total_earnings` after a delegator claims @@ -83,16 +87,53 @@ pub struct Pool { } impl Pool { - fn shares_to_issue(&self, new_funds: BalanceOf, pool_free: BalanceOf) -> SharesOf { - let shares_per_balance = { - // TODO if balance.is_zero() || shares.is_zero() - let balance = T::BalanceToU128::convert(pool_free); + /// Get the amount of shares to issue for some new funds that will be bonded + /// in the pool. + /// + /// * `new_funds`: Incoming funds to be bonded against the pool. + /// * `bonded_balance`: Current bonded balance of the pool. + fn shares_to_issue( + &self, + new_funds: BalanceOf, + bonded_balance: BalanceOf, + ) -> SharesOf { + if bonded_balance.is_zero() || self.shares.is_zero() { + debug_assert!(bonded_balance.is_zero() && self.shares.is_zero()); + + // all pools start with a 1:1 ratio of balance:shares + new_funds + } else { + let shares_per_balance = { + let balance = T::BalanceToU128::convert(bonded_balance); + let shares = T::BalanceToU128::convert(self.shares); + FixedU128::saturating_from_rational(shares, balance) + }; + let new_funds = T::BalanceToU128::convert(new_funds); + + T::U128ToBalance::convert(shares_per_balance.saturating_mul_int(new_funds)) + } + } + + /// Based on the given shares, unbond the equivalent balance, update the pool accordingly, and + /// return the balance unbonded. + fn balance_to_unbond( + &self, + delegator_shares: SharesOf, + bonded_balance: BalanceOf, + ) -> BalanceOf { + if bonded_balance.is_zero() || delegator_shares.is_zero() { + // There is nothing to unbond + return Zero::zero(); + } + + let balance_per_share = { + let balance = T::BalanceToU128::convert(bonded_balance); let shares = T::BalanceToU128::convert(self.shares); - FixedU128::saturating_from_rational(shares, balance) + FixedU128::saturating_from_rational(balance, shares) }; - let new_funds = T::BalanceToU128::convert(new_funds); + let delegator_shares = T::BalanceToU128::convert(delegator_shares); - T::U128ToBalance::convert(shares_per_balance.saturating_mul_int(new_funds)) + T::U128ToBalance::convert(balance_per_share.saturating_mul_int(delegator_shares)) } } @@ -113,16 +154,93 @@ pub struct RewardPool { } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] -struct UnbondPool { +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +struct UnbondPool { + shares: SharesOf, + balance: BalanceOf, +} + +impl UnbondPool { + fn shares_to_issue(&self, new_funds: BalanceOf) -> SharesOf { + if self.balance.is_zero() || self.shares.is_zero() { + debug_assert!(self.balance.is_zero() && self.shares.is_zero()); + + // all pools start with a 1:1 ratio of balance:shares + new_funds + } else { + let shares_per_balance = { + let balance = T::BalanceToU128::convert(self.balance); + let shares = T::BalanceToU128::convert(self.shares); + FixedU128::saturating_from_rational(shares, balance) + }; + let new_funds = T::BalanceToU128::convert(new_funds); + T::U128ToBalance::convert(shares_per_balance.saturating_mul_int(new_funds)) + } + } } +impl Default for UnbondPool { + fn default() -> Self { + Self { shares: Zero::zero(), balance: Zero::zero() } + } +} #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] #[codec(mel_bound(T: Config))] -struct SubPools { - unbonding: UnbondPool, - unbonding: BTreeMap +#[scale_info(skip_type_params(T))] +struct SubPoolContainer { + /// A general, era agnostic pool of funds that have fully unbonded. The pools + /// of `self.with_era` will lazily be merged into into this pool if they are + /// older then `current_era - T::MAX_UNBONDING`. + no_era: UnbondPool, + /// Map of era => unbond pools. + with_era: SubPoolsWithEra, +} + +impl SubPoolContainer { + /// Merge the oldest unbonding pool with an era into the general unbond pool with no associated + /// era. + fn maybe_merge_pools(mut self, current_era: T::EraIndex) -> Self { + if current_era < T::MAX_UNBONDING.into() { + // For the first `T::MAX_UNBONDING` eras of the chain we don't need to do anything. + // I.E. if `MAX_UNBONDING` is 5 and we are in era 4 we can add a pool for this era and + // have exactly `MAX_UNBONDING` pools. + return self; + } + + // I.E. if `MAX_UNBONDING` is 5 and current era is 10, we only want to retain pools 6..=10. + let oldest_era_to_keep = current_era - (T::MAX_UNBONDING.saturating_add(1)).into(); + + let eras_to_remove: Vec<_> = + self.with_era.keys().cloned().filter(|era| *era < oldest_era_to_keep).collect(); + for era in eras_to_remove { + if let Some(p) = self.with_era.remove(&era) { + self.no_era.shares.saturating_add(p.shares); + self.no_era.balance.saturating_add(p.balance); + } else { + // lol + } + } + + self + } +} + +// TODO figure out why the Default derive did not work for SubPoolContainer +impl sp_std::default::Default for SubPoolContainer { + fn default() -> Self { + Self { no_era: UnbondPool::::default(), with_era: SubPoolsWithEra::::default() } + } +} + +// Wrapper for `T::MAX_UNBONDING` to satisfy `trait Get`. +pub struct MaxUnbonding(PhantomData); +impl Get for MaxUnbonding { + fn get() -> u32 { + T::MAX_UNBONDING + } } #[frame_support::pallet] @@ -171,7 +289,7 @@ pub mod pallet { /// The maximum amount of eras an unbonding pool can exist prior to being merged with the /// "average" (TODO need better terminology) unbonding pool. - const MAX_UNBOND_POOL_LIFE: u32; + const MAX_UNBONDING: u32; // MaxPools } @@ -191,10 +309,11 @@ pub mod pallet { pub(crate) type RewardPools = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool>; - // /// Groups of unbonding pools. Each group of unbonding pools belongs to a primary pool, - // /// hence the name sub-pools. - // #[pallet::storage] - // type SubPools = CountedStorageMap<_, PoolId, SubPools, _>; + /// Groups of unbonding pools. Each group of unbonding pools belongs to a primary pool, + /// hence the name sub-pools. + #[pallet::storage] + pub(crate) type SubPools = + CountedStorageMap<_, Twox64Concat, PoolId, SubPoolContainer>; #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] @@ -257,8 +376,9 @@ pub mod pallet { // we get the exact amount we can bond extra let exact_amount_to_bond = new_free_balance.saturating_sub(old_free_balance); + let bonded_balance = T::NominationProvider::bonded_balance(&primary_pool.account_id); // issue the new shares - let new_shares = primary_pool.shares_to_issue(exact_amount_to_bond, old_free_balance); + let new_shares = primary_pool.shares_to_issue(exact_amount_to_bond, bonded_balance); primary_pool.shares = primary_pool.shares.saturating_add(new_shares); let delegator = Delegator:: { pool: target, @@ -294,12 +414,9 @@ pub mod pallet { /// This extrinsic is permisionless in the sense that any account can call it for any /// delegator in the system. #[pallet::weight(666)] - pub fn claim_payout_other(origin: OriginFor, account: T::AccountId) -> DispatchResult { - ensure_signed(origin)?; - - // READ THINGS FROM STORAGE - let mut delegator = - Delegators::::get(&account).ok_or(Error::::DelegatorNotFound)?; + pub fn claim_payout_other(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let mut delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; // If the delegator is unbonding they cannot claim rewards. Note that when the delagator // goes to unbond, the unbond function should claim rewards for the final time. @@ -322,8 +439,6 @@ pub mod pallet { })?; let current_balance = T::Currency::free_balance(&reward_pool.account_id); - // DO MATHS TO CALCULATE PAYOUT - // The earnings since the last claimed payout let new_earnings = current_balance.saturating_sub(reward_pool.balance); @@ -358,10 +473,10 @@ pub mod pallet { T::U128ToBalance::convert(payout) }; - // TRANSFER PAYOUT TO DELEGATOR + // Transfer payout to the delegator. note if this succeeds we don't want to fail after. T::Currency::transfer( &reward_pool.account_id, - &account, + &who, delegator_payout, // TODO double check we are ok with dusting the account - If their is a very high // ED this could lead to a non-negligible loss of rewards @@ -369,20 +484,20 @@ pub mod pallet { )?; Self::deposit_event(Event::::Payout { - delegator: account.clone(), + delegator: who.clone(), pool: delegator.pool, payout: delegator_payout, }); - // RECORD RELEVANT UPDATES + // Record updates delegator.reward_pool_total_earnings = new_total_earnings; reward_pool.shares = current_shares.saturating_sub(delegator_virtual_shares); reward_pool.balance = current_balance; reward_pool.total_earnings = new_total_earnings; - // WRITE UPDATED DELEGATOR AND REWARD POOL TO STORAGE + // Write the updated delegator and reward pool to storage RewardPools::insert(delegator.pool, reward_pool); - Delegators::insert(account.clone(), delegator); + Delegators::insert(who, delegator); Ok(()) } @@ -396,16 +511,54 @@ pub mod pallet { // Unbonding is all or nothing and a delegator can only belong to 1 pool. ensure!(delegator.unbonding_era.is_none(), Error::::AlreadyUnbonding); - let primary_pool = PrimaryPools::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; - let sub_pools = SubPools::::get(delegator.pool).ok_or_else(|| { - // make sub pool - })?; - + let mut primary_pool = + PrimaryPools::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; + // Note that we lazily create the unbonding pools here if they don't already exist + let sub_pools = SubPools::::get(delegator.pool).unwrap_or_default(); + // TODO double check if we need to count for elections when + // the unbonding era. + let current_era = T::NominationProvider::current_era(); + + let bonded_balance = T::NominationProvider::bonded_balance(&primary_pool.account_id); + let balance_to_unbond = + primary_pool.balance_to_unbond(delegator.shares, bonded_balance); + + // Update the primary pool. Note that we must do this *after* calculating the balance + // to unbond. + primary_pool.shares = primary_pool.shares.saturating_sub(delegator.shares); + // Unbond in the actual underlying pool + // TODO - we can only do this for as many locking chunks are accepted + T::NominationProvider::unbond(&primary_pool.account_id, balance_to_unbond)?; + + // Merge any older pools into the general, era agnostic unbond pool. Note that we do + // this before inserting to ensure we don't go over the max unbonding pools. + let mut sub_pools = sub_pools.maybe_merge_pools(current_era); + + // Update the unbond pool associated with the current era with the + // unbonded funds. Note that we lazily create the unbond pool if it + // does not yet exist. + // let unbond_pool = sub_pools + // .with_era + // .entry(current_era) + // .or_insert_with(|| UnbondPool::::default()); + { + let unbond_pool = sub_pools.with_era.get_mut(¤t_era).unwrap(); + let shares_to_issue = unbond_pool.shares_to_issue(balance_to_unbond); + unbond_pool.shares = unbond_pool.shares.saturating_add(shares_to_issue); + unbond_pool.balance = unbond_pool.balance.saturating_add(balance_to_unbond); + } + + delegator.unbonding_era = Some(current_era); + + // Write the updated delegator, primary pool, and sub pool to storage + PrimaryPools::insert(delegator.pool, primary_pool); + SubPools::insert(delegator.pool, sub_pools); + Delegators::insert(who, delegator); // TODO claim rewards (will need to refactor the rewards function) Ok(()) } - } + } } // impl Pallet { diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index e45a21ab25036..ad493fb168c6f 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -880,7 +880,17 @@ pub mod pallet { // Note: in case there is no current era it is fine to bond one era more. let era = Self::current_era().unwrap_or(0) + T::BondingDuration::get(); - ledger.unlocking.push(UnlockChunk { value, era }); + if let Some(mut chunk) = ledger.unlocking.last_mut() { + if chunk.era == era { + // To keep the chunk count down, we only keep one chunk per era. Since + // unlocking is a queue, if a chunk exists for the era we know that it would + // be the last one + use sp_runtime::traits::Saturating; + chunk.value = chunk.value.saturating_add(value) + } + } else { + ledger.unlocking.push(UnlockChunk { value, era }); + }; // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); @@ -921,24 +931,23 @@ pub mod pallet { ledger = ledger.consolidate_unlocked(current_era) } - let post_info_weight = if ledger.unlocking.is_empty() && - ledger.active < T::Currency::minimum_balance() - { - // This account must have called `unbond()` with some value that caused the active - // portion to fall below existential deposit + will have no more unlocking chunks - // left. We can now safely remove all staking-related information. - Self::kill_stash(&stash, num_slashing_spans)?; - // Remove the lock. - T::Currency::remove_lock(STAKING_ID, &stash); - // This is worst case scenario, so we use the full weight and return None - None - } else { - // This was the consequence of a partial unbond. just update the ledger and move on. - Self::update_ledger(&controller, &ledger); + let post_info_weight = + if ledger.unlocking.is_empty() && ledger.active < T::Currency::minimum_balance() { + // This account must have called `unbond()` with some value that caused the active + // portion to fall below existential deposit + will have no more unlocking chunks + // left. We can now safely remove all staking-related information. + Self::kill_stash(&stash, num_slashing_spans)?; + // Remove the lock. + T::Currency::remove_lock(STAKING_ID, &stash); + // This is worst case scenario, so we use the full weight and return None + None + } else { + // This was the consequence of a partial unbond. just update the ledger and move on. + Self::update_ledger(&controller, &ledger); - // This is only an update, so we use less overall weight. - Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) - }; + // This is only an update, so we use less overall weight. + Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) + }; // `old_total` should never be less than the new total because // `consolidate_unlocked` strictly subtracts balance. @@ -1464,8 +1473,8 @@ pub mod pallet { let _ = ensure_signed(origin)?; let ed = T::Currency::minimum_balance(); - let reapable = T::Currency::total_balance(&stash) < ed || - Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) + let reapable = T::Currency::total_balance(&stash) < ed + || Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) .map(|l| l.total) .unwrap_or_default() < ed; ensure!(reapable, Error::::FundedTarget); From 002bc149cf8b85808c9a848a3a21540ce223d220 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 13 Jan 2022 17:40:16 -0800 Subject: [PATCH 007/299] Break up claim_payout --- frame/pools/src/lib.rs | 143 ++++++++++++++++++++++++----------------- 1 file changed, 85 insertions(+), 58 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index a86fc36552c30..42a70d6bd8e2c 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -414,13 +414,9 @@ pub mod pallet { /// This extrinsic is permisionless in the sense that any account can call it for any /// delegator in the system. #[pallet::weight(666)] - pub fn claim_payout_other(origin: OriginFor) -> DispatchResult { + pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let mut delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; - - // If the delegator is unbonding they cannot claim rewards. Note that when the delagator - // goes to unbond, the unbond function should claim rewards for the final time. - ensure!(delegator.unbonding_era.is_none(), Error::::AlreadyUnbonding); + let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; let primary_pool = PrimaryPools::::get(&delegator.pool).ok_or_else(|| { log!(error, "A primary pool could not be found, this is a system logic error."); debug_assert!( @@ -429,7 +425,7 @@ pub mod pallet { ); Error::::PoolNotFound })?; - let mut reward_pool = RewardPools::::get(&delegator.pool).ok_or_else(|| { + let reward_pool = RewardPools::::get(&delegator.pool).ok_or_else(|| { log!(error, "A reward pool could not be found, this is a system logic error."); debug_assert!( false, @@ -437,64 +433,18 @@ pub mod pallet { ); Error::::RewardPoolNotFound })?; - let current_balance = T::Currency::free_balance(&reward_pool.account_id); - - // The earnings since the last claimed payout - let new_earnings = current_balance.saturating_sub(reward_pool.balance); - - // The lifetime earnings of the of the reward pool - let new_total_earnings = new_earnings.saturating_add(reward_pool.total_earnings); - - // The new shares that will be added to the pool. For every unit of balance that has - // been earned by the reward pool, we inflate the reward pool shares by - // `primary_pool.total_shares`. In effect this allows each, single unit of balance (e.g. - // plank) to be divvied up pro-rata among delegators based on shares. - // TODO this needs to be some sort of BigUInt arithmetic - let new_shares = primary_pool.shares.saturating_mul(new_earnings); - - // The shares of the reward pool after taking into account the new earnings - let current_shares = reward_pool.shares.saturating_add(new_shares); - - // The rewards pool's earnings since the last time this delegator claimed a payout - let new_earnings_since_last_claim = - new_total_earnings.saturating_sub(delegator.reward_pool_total_earnings); - // The shares of the reward pool that belong to the delegator. - let delegator_virtual_shares = - delegator.shares.saturating_mul(new_earnings_since_last_claim); - - let delegator_payout = { - let delegator_ratio_of_shares = FixedU128::saturating_from_rational( - T::BalanceToU128::convert(delegator_virtual_shares), - T::BalanceToU128::convert(current_shares), - ); - let payout = delegator_ratio_of_shares - .saturating_mul_int(T::BalanceToU128::convert(current_balance)); - T::U128ToBalance::convert(payout) - }; + let (reward_pool, delegator, delegator_payout) = + Self::calculate_delegator_payout(&primary_pool, reward_pool, delegator)?; // Transfer payout to the delegator. note if this succeeds we don't want to fail after. - T::Currency::transfer( + Self::transfer_reward( &reward_pool.account_id, - &who, + who.clone(), + delegator.pool, delegator_payout, - // TODO double check we are ok with dusting the account - If their is a very high - // ED this could lead to a non-negligible loss of rewards - ExistenceRequirement::AllowDeath, // Dust may be lost here )?; - Self::deposit_event(Event::::Payout { - delegator: who.clone(), - pool: delegator.pool, - payout: delegator_payout, - }); - - // Record updates - delegator.reward_pool_total_earnings = new_total_earnings; - reward_pool.shares = current_shares.saturating_sub(delegator_virtual_shares); - reward_pool.balance = current_balance; - reward_pool.total_earnings = new_total_earnings; - // Write the updated delegator and reward pool to storage RewardPools::insert(delegator.pool, reward_pool); Delegators::insert(who, delegator); @@ -561,6 +511,83 @@ pub mod pallet { } } +impl Pallet { + /// Calculate the rewards for `delegator`. This is does no + fn calculate_delegator_payout( + primary_pool: &Pool, + mut reward_pool: RewardPool, + mut delegator: Delegator, + ) -> Result<(RewardPool, Delegator, BalanceOf), DispatchError> { + // If the delegator is unbonding they cannot claim rewards. Note that when the delagator + // goes to unbond, the unbond function should claim rewards for the final time. + ensure!(delegator.unbonding_era.is_none(), Error::::AlreadyUnbonding); + + let current_balance = T::Currency::free_balance(&reward_pool.account_id); + + // The earnings since the last claimed payout + let new_earnings = current_balance.saturating_sub(reward_pool.balance); + + // The lifetime earnings of the of the reward pool + let new_total_earnings = new_earnings.saturating_add(reward_pool.total_earnings); + + // The new shares that will be added to the pool. For every unit of balance that has + // been earned by the reward pool, we inflate the reward pool shares by + // `primary_pool.total_shares`. In effect this allows each, single unit of balance (e.g. + // plank) to be divvied up pro-rata among delegators based on shares. + // TODO this needs to be some sort of BigUInt arithmetic + let new_shares = primary_pool.shares.saturating_mul(new_earnings); + + // The shares of the reward pool after taking into account the new earnings + let current_shares = reward_pool.shares.saturating_add(new_shares); + + // The rewards pool's earnings since the last time this delegator claimed a payout + let new_earnings_since_last_claim = + new_total_earnings.saturating_sub(delegator.reward_pool_total_earnings); + // The shares of the reward pool that belong to the delegator. + let delegator_virtual_shares = + delegator.shares.saturating_mul(new_earnings_since_last_claim); + + let delegator_payout = { + let delegator_ratio_of_shares = FixedU128::saturating_from_rational( + T::BalanceToU128::convert(delegator_virtual_shares), + T::BalanceToU128::convert(current_shares), + ); + + let payout = delegator_ratio_of_shares + .saturating_mul_int(T::BalanceToU128::convert(current_balance)); + T::U128ToBalance::convert(payout) + }; + + // Record updates + delegator.reward_pool_total_earnings = new_total_earnings; + reward_pool.shares = current_shares.saturating_sub(delegator_virtual_shares); + reward_pool.balance = current_balance; + reward_pool.total_earnings = new_total_earnings; + + Ok((reward_pool, delegator, delegator_payout)) + } + + /// Transfer the delegator their payout from the pool and deposit the corresponding event. + fn transfer_reward( + reward_pool: &T::AccountId, + delegator: T::AccountId, + pool: PoolId, + payout: BalanceOf, + ) -> Result<(), DispatchError> { + T::Currency::transfer( + reward_pool, + &delegator, + payout, + // TODO double check we are ok with dusting the account - If their is a very high + // ED this could lead to a non-negligible loss of rewards + ExistenceRequirement::AllowDeath, // Dust may be lost here + )?; + Self::deposit_event(Event::::Payout { delegator, pool, payout }); + + Ok(()) + } +} + // impl Pallet { // do_create_pool( // creator: T::AccountId, From 4cdb6dd55c554c497090ed4468ed599e637dbb01 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 13 Jan 2022 21:58:49 -0800 Subject: [PATCH 008/299] Encapsulate core payout logic with storage writes --- frame/pools/src/lib.rs | 74 ++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 42a70d6bd8e2c..586a63a4ebb22 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -425,29 +425,8 @@ pub mod pallet { ); Error::::PoolNotFound })?; - let reward_pool = RewardPools::::get(&delegator.pool).ok_or_else(|| { - log!(error, "A reward pool could not be found, this is a system logic error."); - debug_assert!( - false, - "A reward pool could not be found, this is a system logic error." - ); - Error::::RewardPoolNotFound - })?; - - let (reward_pool, delegator, delegator_payout) = - Self::calculate_delegator_payout(&primary_pool, reward_pool, delegator)?; - - // Transfer payout to the delegator. note if this succeeds we don't want to fail after. - Self::transfer_reward( - &reward_pool.account_id, - who.clone(), - delegator.pool, - delegator_payout, - )?; - // Write the updated delegator and reward pool to storage - RewardPools::insert(delegator.pool, reward_pool); - Delegators::insert(who, delegator); + Self::do_reward_payout(who, delegator, &primary_pool)?; Ok(()) } @@ -456,13 +435,17 @@ pub mod pallet { #[pallet::weight(666)] pub fn unbond(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let mut delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; - - // Unbonding is all or nothing and a delegator can only belong to 1 pool. - ensure!(delegator.unbonding_era.is_none(), Error::::AlreadyUnbonding); - + let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; let mut primary_pool = PrimaryPools::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; + + // Claim the the payout prior to unbonding. Once the user is unbonding their shares + // no longer exist in the primary pool and thus they can no longer claim their payouts. + // It is not strictly necessary to claim the rewards, but we do it here for UX. + Self::do_reward_payout(who.clone(), delegator, &primary_pool)?; + + // Re-fetch the delegator because they where updated by `do_reward_payout`. + let mut delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPools::::get(delegator.pool).unwrap_or_default(); // TODO double check if we need to count for elections when @@ -476,7 +459,8 @@ pub mod pallet { // Update the primary pool. Note that we must do this *after* calculating the balance // to unbond. primary_pool.shares = primary_pool.shares.saturating_sub(delegator.shares); - // Unbond in the actual underlying pool + + // Unbond in the actual underlying pool - we can't fail after this // TODO - we can only do this for as many locking chunks are accepted T::NominationProvider::unbond(&primary_pool.account_id, balance_to_unbond)?; @@ -500,19 +484,18 @@ pub mod pallet { delegator.unbonding_era = Some(current_era); - // Write the updated delegator, primary pool, and sub pool to storage + // Now that we know everything has worked write the items to storage. PrimaryPools::insert(delegator.pool, primary_pool); SubPools::insert(delegator.pool, sub_pools); Delegators::insert(who, delegator); - // TODO claim rewards (will need to refactor the rewards function) Ok(()) } } } impl Pallet { - /// Calculate the rewards for `delegator`. This is does no + /// Calculate the rewards for `delegator`. fn calculate_delegator_payout( primary_pool: &Pool, mut reward_pool: RewardPool, @@ -586,6 +569,35 @@ impl Pallet { Ok(()) } + + fn do_reward_payout( + delegator_id: T::AccountId, + delegator: Delegator, + primary_pool: &Pool, + ) -> DispatchResult { + let reward_pool = RewardPools::::get(&delegator.pool).ok_or_else(|| { + log!(error, "A reward pool could not be found, this is a system logic error."); + debug_assert!(false, "A reward pool could not be found, this is a system logic error."); + Error::::RewardPoolNotFound + })?; + + let (reward_pool, delegator, delegator_payout) = + Self::calculate_delegator_payout(primary_pool, reward_pool, delegator)?; + + // Transfer payout to the delegator. + Self::transfer_reward( + &reward_pool.account_id, + delegator_id.clone(), + delegator.pool, + delegator_payout, + )?; + + // Write the updated delegator and reward pool to storage + RewardPools::insert(delegator.pool, reward_pool); + Delegators::insert(delegator_id, delegator); + + Ok(()) + } } // impl Pallet { From 9ec6468bedbc5d81d20166350af7100b2b094e56 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 13 Jan 2022 22:44:12 -0800 Subject: [PATCH 009/299] MVP withdraw_unbonded --- frame/pools/src/lib.rs | 66 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 586a63a4ebb22..6d628692f542c 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -59,6 +59,9 @@ pub trait NominationProviderTrait { fn bond_extra(controller: &AccountId, extra: Balance) -> DispatchResult; fn unbond(controller: &AccountId, value: Balance) -> DispatchResult; + + /// Number of eras that staked funds must remain bonded for. + fn bond_duration() -> EraIndex; } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] @@ -179,6 +182,22 @@ impl UnbondPool { T::U128ToBalance::convert(shares_per_balance.saturating_mul_int(new_funds)) } } + + fn balance_to_unbond(&self, delegator_shares: SharesOf) -> BalanceOf { + if self.balance.is_zero() || delegator_shares.is_zero() { + // There is nothing to unbond + return Zero::zero(); + } + + let balance_per_share = { + let balance = T::BalanceToU128::convert(self.balance); + let shares = T::BalanceToU128::convert(self.shares); + FixedU128::saturating_from_rational(balance, shares) + }; + let delegator_shares = T::BalanceToU128::convert(delegator_shares); + + T::U128ToBalance::convert(balance_per_share.saturating_mul_int(delegator_shares)) + } } impl Default for UnbondPool { @@ -340,6 +359,10 @@ pub mod pallet { InsufficientBond, /// The delegator is already unbonding. AlreadyUnbonding, + /// The delegator is not unbonding and thus cannot withdraw funds. + NotUnbonding, + /// Unbonded funds cannot be withdrawn yet because the bond duration has not passed. + NotUnbondedYet, } #[pallet::call] @@ -491,6 +514,49 @@ pub mod pallet { Ok(()) } + + #[pallet::weight(666)] + pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let delegator = Delegators::::take(&who).ok_or(Error::::DelegatorNotFound)?; + + let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; + let current_era = T::NominationProvider::current_era(); + if current_era.saturating_sub(unbonding_era) < T::NominationProvider::bond_duration() { + return Err(Error::::NotUnbondedYet.into()); + }; + + let mut sub_pools = SubPools::::get(delegator.pool).unwrap_or_default(); + + let balance_to_unbond = if let Some(pool) = sub_pools.with_era.get_mut(¤t_era) { + let balance_to_unbond = pool.balance_to_unbond(delegator.shares); + pool.shares = pool.shares.saturating_sub(delegator.shares); + pool.balance = pool.balance.saturating_sub(balance_to_unbond); + + balance_to_unbond + } else { + // A pool does not belong to this era, so it must have been merged to the era-less pool. + let balance_to_unbond = sub_pools.no_era.balance_to_unbond(delegator.shares); + sub_pools.no_era.shares = sub_pools.no_era.shares.saturating_sub(delegator.shares); + sub_pools.no_era.balance = + sub_pools.no_era.balance.saturating_sub(balance_to_unbond); + + balance_to_unbond + }; + + let primary_pool = + PrimaryPools::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; + T::Currency::transfer( + &primary_pool.account_id, + &who, + balance_to_unbond, + ExistenceRequirement::AllowDeath, + )?; + + SubPools::::insert(delegator.pool, sub_pools); + + Ok(()) + } } } From b21fb5b9da444e9551de64777476095f99dfb5a5 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 14 Jan 2022 12:53:31 +0000 Subject: [PATCH 010/299] feedback --- frame/pools/src/lib.rs | 116 ++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 59 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 6d628692f542c..110ffdec84a23 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -14,6 +14,7 @@ use frame_support::{ pallet_prelude::*, storage::bounded_btree_map::BoundedBTreeMap, traits::{Currency, ExistenceRequirement, Get}, + DefaultNoBound, }; use scale_info::TypeInfo; use sp_arithmetic::{FixedPointNumber, FixedU128}; @@ -28,7 +29,7 @@ macro_rules! log { ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { log::$level!( target: crate::LOG_TARGET, - concat!("[{:?}] 👜", $patter), >::block_number() $(, $values)* + concat!("[{:?}] 💰", $patter), >::block_number() $(, $values)* ) }; } @@ -39,18 +40,21 @@ type BalanceOf = type SharesOf = BalanceOf; type SubPoolsWithEra = BoundedBTreeMap<::EraIndex, UnbondPool, MaxUnbonding>; -pub trait NominationProviderTrait { +/// Trait for communication with the staking pallet. +pub trait StakingInterface { /// The minimum amount necessary to bond to be a nominator. This does not necessarily mean the /// nomination will be counted in an election, but instead just enough to be stored as a /// nominator (e.g. in the bags-list of polkadot) fn minimum_bond() -> Balance; - /// The current era for the elections system + /// The current era for the elections system. fn current_era() -> EraIndex; /// Wether or not the elections system has an ongoing election. If there is an ongoing election /// it is assumed that any new pool joiner's funds will not start earning rewards until the /// following era. + // TODO: a main advantage of our snapshot system is that we should not care about when an + // election is ongoing. I hope and predict that this won't be needed or is being use by mistake. fn is_ongoing_election() -> bool; /// Balance `controller` has bonded for nominating. @@ -72,9 +76,9 @@ pub struct Delegator { /// The quantity of shares this delegator has in the p shares: SharesOf, /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. - /// Assumiing no massive burning events, we expect this value to always be below total - /// issuance. This value lines up with the `RewardPool.total_earnings` after a delegator claims - /// a payout. TODO ^ double check the above is an OK assumption + /// Assuming no massive burning events, we expect this value to always be below total issuance. + /// This value lines up with the `RewardPool.total_earnings` after a delegator claims a payout. + /// TODO ^ double check the above is an OK assumption reward_pool_total_earnings: BalanceOf, /// The era this delegator started unbonding at. unbonding_era: Option, @@ -90,16 +94,13 @@ pub struct Pool { } impl Pool { - /// Get the amount of shares to issue for some new funds that will be bonded - /// in the pool. + /// Get the amount of shares to issue for some new funds that will be bonded in the pool. /// /// * `new_funds`: Incoming funds to be bonded against the pool. - /// * `bonded_balance`: Current bonded balance of the pool. - fn shares_to_issue( - &self, - new_funds: BalanceOf, - bonded_balance: BalanceOf, - ) -> SharesOf { + fn shares_to_issue(&self, new_funds: BalanceOf) -> SharesOf { + // TODO: this is the great thing about having in storage items, you have access + // to lots of more things. + let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); if bonded_balance.is_zero() || self.shares.is_zero() { debug_assert!(bonded_balance.is_zero() && self.shares.is_zero()); @@ -117,16 +118,15 @@ impl Pool { } } + /// TODO: doc seems wrong, this just converts shares -> balance, not actually updating anything. + /// /// Based on the given shares, unbond the equivalent balance, update the pool accordingly, and /// return the balance unbonded. - fn balance_to_unbond( - &self, - delegator_shares: SharesOf, - bonded_balance: BalanceOf, - ) -> BalanceOf { + fn balance_to_unbond(&self, delegator_shares: SharesOf) -> BalanceOf { + let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); if bonded_balance.is_zero() || delegator_shares.is_zero() { // There is nothing to unbond - return Zero::zero(); + return Zero::zero() } let balance_per_share = { @@ -156,7 +156,10 @@ pub struct RewardPool { account_id: T::AccountId, } -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] +// TODO: the default or other derives don't work because the default derive will only be valid when +// all the generics are also `Default`. In this case `T: Default` does not hold and therefore does +// not work. For this, we have various `NoBound` derives in support. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound)] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] struct UnbondPool { @@ -186,7 +189,7 @@ impl UnbondPool { fn balance_to_unbond(&self, delegator_shares: SharesOf) -> BalanceOf { if self.balance.is_zero() || delegator_shares.is_zero() { // There is nothing to unbond - return Zero::zero(); + return Zero::zero() } let balance_per_share = { @@ -200,16 +203,10 @@ impl UnbondPool { } } -impl Default for UnbondPool { - fn default() -> Self { - Self { shares: Zero::zero(), balance: Zero::zero() } - } -} - -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound)] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] -struct SubPoolContainer { +struct SubPoolsContainer { /// A general, era agnostic pool of funds that have fully unbonded. The pools /// of `self.with_era` will lazily be merged into into this pool if they are /// older then `current_era - T::MAX_UNBONDING`. @@ -218,7 +215,7 @@ struct SubPoolContainer { with_era: SubPoolsWithEra, } -impl SubPoolContainer { +impl SubPoolsContainer { /// Merge the oldest unbonding pool with an era into the general unbond pool with no associated /// era. fn maybe_merge_pools(mut self, current_era: T::EraIndex) -> Self { @@ -226,7 +223,7 @@ impl SubPoolContainer { // For the first `T::MAX_UNBONDING` eras of the chain we don't need to do anything. // I.E. if `MAX_UNBONDING` is 5 and we are in era 4 we can add a pool for this era and // have exactly `MAX_UNBONDING` pools. - return self; + return self } // I.E. if `MAX_UNBONDING` is 5 and current era is 10, we only want to retain pools 6..=10. @@ -247,14 +244,10 @@ impl SubPoolContainer { } } -// TODO figure out why the Default derive did not work for SubPoolContainer -impl sp_std::default::Default for SubPoolContainer { - fn default() -> Self { - Self { no_era: UnbondPool::::default(), with_era: SubPoolsWithEra::::default() } - } -} - // Wrapper for `T::MAX_UNBONDING` to satisfy `trait Get`. +// TODO: my 2 cents on making life harder for yourself with the `const` and manually wrapping it in +// Get: just don't, not worth the effort and you will probably regret it when wanting to make these +// values dynamic in tests. pub struct MaxUnbonding(PhantomData); impl Get for MaxUnbonding { fn get() -> u32 { @@ -289,7 +282,12 @@ pub mod pallet { // Infallible method for converting `u128` to `Currency::Balance`. type U128ToBalance: Convert>; - /// The type for unique era indexes. Likely comes from what implements `NominationProvider`. + // TODO: This should 200% be an associated type of the `StakingInterface`. + // also, is EraIndex really generic anywhere? I thought it is pretty hardcoded to be u32 + // somewhere in `sp-staking`. We could move it there and make everything easier. (again, + // this can be a backport PR to master ahead of time) + + /// The type for unique era indexes. Likely comes from what implements `StakingInterface`. type EraIndex: Member + Parameter + AtLeast32BitUnsigned @@ -299,15 +297,17 @@ pub mod pallet { + MaxEncodedLen + TypeInfo; + // TODO: learning from my mistakes, you probably will have an easier time if you make all of + // these be associated types and just constrain them here, so you won't need name it when + // you do fully-qualified syntax e.g. `type StakingInterface: StakingInterface, AccountId = Self::AccountId, EraIndex = Self::EraIndex>;` + /// The interface for nominating. - type NominationProvider: NominationProviderTrait< - BalanceOf, - Self::AccountId, - Self::EraIndex, - >; + type StakingInterface: StakingInterface, Self::AccountId, Self::EraIndex>; /// The maximum amount of eras an unbonding pool can exist prior to being merged with the /// "average" (TODO need better terminology) unbonding pool. + // TODO: kian yelling don't do this to yourself :D const MAX_UNBONDING: u32; // MaxPools @@ -332,7 +332,7 @@ pub mod pallet { /// hence the name sub-pools. #[pallet::storage] pub(crate) type SubPools = - CountedStorageMap<_, Twox64Concat, PoolId, SubPoolContainer>; + CountedStorageMap<_, Twox64Concat, PoolId, SubPoolsContainer>; #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] @@ -380,7 +380,7 @@ pub mod pallet { // And that `amount` will meet the minimum bond let old_free_balance = T::Currency::free_balance(&primary_pool.account_id); ensure!( - old_free_balance.saturating_add(amount) >= T::NominationProvider::minimum_bond(), + old_free_balance.saturating_add(amount) >= T::StakingInterface::minimum_bond(), Error::::InsufficientBond ); @@ -399,9 +399,8 @@ pub mod pallet { // we get the exact amount we can bond extra let exact_amount_to_bond = new_free_balance.saturating_sub(old_free_balance); - let bonded_balance = T::NominationProvider::bonded_balance(&primary_pool.account_id); // issue the new shares - let new_shares = primary_pool.shares_to_issue(exact_amount_to_bond, bonded_balance); + let new_shares = primary_pool.shares_to_issue(exact_amount_to_bond); primary_pool.shares = primary_pool.shares.saturating_add(new_shares); let delegator = Delegator:: { pool: target, @@ -413,7 +412,7 @@ pub mod pallet { }; // Do bond extra - T::NominationProvider::bond_extra(&primary_pool.account_id, exact_amount_to_bond)?; + T::StakingInterface::bond_extra(&primary_pool.account_id, exact_amount_to_bond)?; // Write the pool and delegator to storage Delegators::insert(who.clone(), delegator); @@ -473,11 +472,9 @@ pub mod pallet { let sub_pools = SubPools::::get(delegator.pool).unwrap_or_default(); // TODO double check if we need to count for elections when // the unbonding era. - let current_era = T::NominationProvider::current_era(); + let current_era = T::StakingInterface::current_era(); - let bonded_balance = T::NominationProvider::bonded_balance(&primary_pool.account_id); - let balance_to_unbond = - primary_pool.balance_to_unbond(delegator.shares, bonded_balance); + let balance_to_unbond = primary_pool.balance_to_unbond(delegator.shares); // Update the primary pool. Note that we must do this *after* calculating the balance // to unbond. @@ -485,7 +482,7 @@ pub mod pallet { // Unbond in the actual underlying pool - we can't fail after this // TODO - we can only do this for as many locking chunks are accepted - T::NominationProvider::unbond(&primary_pool.account_id, balance_to_unbond)?; + T::StakingInterface::unbond(&primary_pool.account_id, balance_to_unbond)?; // Merge any older pools into the general, era agnostic unbond pool. Note that we do // this before inserting to ensure we don't go over the max unbonding pools. @@ -521,9 +518,9 @@ pub mod pallet { let delegator = Delegators::::take(&who).ok_or(Error::::DelegatorNotFound)?; let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; - let current_era = T::NominationProvider::current_era(); - if current_era.saturating_sub(unbonding_era) < T::NominationProvider::bond_duration() { - return Err(Error::::NotUnbondedYet.into()); + let current_era = T::StakingInterface::current_era(); + if current_era.saturating_sub(unbonding_era) < T::StakingInterface::bond_duration() { + return Err(Error::::NotUnbondedYet.into()) }; let mut sub_pools = SubPools::::get(delegator.pool).unwrap_or_default(); @@ -535,7 +532,8 @@ pub mod pallet { balance_to_unbond } else { - // A pool does not belong to this era, so it must have been merged to the era-less pool. + // A pool does not belong to this era, so it must have been merged to the era-less + // pool. let balance_to_unbond = sub_pools.no_era.balance_to_unbond(delegator.shares); sub_pools.no_era.shares = sub_pools.no_era.shares.saturating_sub(delegator.shares); sub_pools.no_era.balance = From be7774c55e8a80f63153cded4be4f177852f32f2 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 14 Jan 2022 12:26:06 -0800 Subject: [PATCH 011/299] save --- frame/pools/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 6d628692f542c..b0fe0130dd4fe 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -666,6 +666,12 @@ impl Pallet { } } +// TODO +// - rebond_rewards +// - force pool creation +// - force pool update +// - force pool delete? + // impl Pallet { // do_create_pool( // creator: T::AccountId, From 434a4078d4935d52e523d5513f3425db6e901898 Mon Sep 17 00:00:00 2001 From: Zeke Mostov <32168567+emostov@users.noreply.github.com> Date: Tue, 18 Jan 2022 18:15:11 -0800 Subject: [PATCH 012/299] Rough initial impl --- Cargo.lock | 5 + Cargo.toml | 2 +- frame/pools/Cargo.toml | 7 + frame/pools/src/lib.rs | 616 +++++++++++------- frame/pools/src/mock.rs | 184 ++++++ frame/pools/src/tests.rs | 167 +++++ frame/staking/src/lib.rs | 56 +- frame/staking/src/pallet/impls.rs | 10 +- frame/staking/src/pallet/mod.rs | 43 +- frame/staking/src/slashing.rs | 32 +- .../support/src/storage/bounded_btree_map.rs | 16 +- primitives/staking/src/lib.rs | 55 ++ 12 files changed, 930 insertions(+), 263 deletions(-) create mode 100644 frame/pools/src/mock.rs create mode 100644 frame/pools/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 4c6a00f529ed0..c7385c9f77586 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5956,11 +5956,16 @@ dependencies = [ "frame-support", "frame-system", "log 0.4.14", + "pallet-balances", "parity-scale-codec", "scale-info", "sp-arithmetic", + "sp-core", + "sp-io", "sp-runtime", + "sp-staking", "sp-std", + "sp-tracing", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b460013d0821e..d91fb2754143f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,9 +101,9 @@ members = [ "frame/nicks", "frame/node-authorization", "frame/offences", - "frame/pools", "frame/preimage", "frame/proxy", + "frame/pools", "frame/randomness-collective-flip", "frame/recovery", "frame/scheduler", diff --git a/frame/pools/Cargo.toml b/frame/pools/Cargo.toml index 2e373d8c9b45b..963d5c95f8dc1 100644 --- a/frame/pools/Cargo.toml +++ b/frame/pools/Cargo.toml @@ -22,10 +22,17 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys sp-arithmetic = { version = "4.0.0", default-features = false, path = "../../primitives/arithmetic" } sp-runtime = { version = "4.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +sp-core = { version = "4.1.0-dev", default-features = false, path = "../../primitives/core" } # third party log = { version = "0.4.14", default-features = false } +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-io = { version = "4.0.0-dev", path = "../../primitives/io"} +sp-tracing = { version = "4.0.0", path = "../../primitives/tracing" } + [features] default = ["std"] std = [ diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 0cda0c89258ef..1e3e3d57bcdf4 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -1,11 +1,27 @@ //! Delegation pools for nominating in `pallet-staking`. //! -//! Each pool is represented by: (the actively staked funds), a rewards pool (the -//! rewards earned by the actively staked funds) and a group of unbonding pools (pools ) +//! The delegation pool abstraction is concretely composed of: //! //! * primary pool: This pool represents the actively staked funds ... //! * rewards pool: The rewards earned by actively staked funds. Delegator can withdraw rewards once -//! they +//! * sub pools: This a group of pools where we have a set of pools organized by era +//! (`SubPoolsContainer.with_era`) and one pool that is not associated with an era +//! (`SubsPoolsContainer.no_era`). Once a `with_era` pool is older then `current_era - +//! MaxUnbonding`, its points and balance get merged into the `no_era` pool. +//! +//! # Joining +//! +//! # Claiming rewards +//! +//! # Unbonding and withdrawing +//! +//! # Slashing +//! +//! # Pool creation +//! +//! # Negatives +//! - no voting +//! - .. #![cfg_attr(not(feature = "std"), no_std)] @@ -18,17 +34,24 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_arithmetic::{FixedPointNumber, FixedU128}; -use sp_runtime::traits::{AtLeast32BitUnsigned, Convert, Saturating, Zero}; +use sp_runtime::traits::{Convert, One, Saturating, StaticLookup, TrailingZeroInput, Zero}; +use sp_staking::{EraIndex, PoolsInterface, StakingInterface}; +use sp_std::collections::btree_map::BTreeMap; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; pub use pallet::*; pub(crate) const LOG_TARGET: &'static str = "runtime::pools"; -// syntactic sugar for logging. +// Syntactic sugar for logging. #[macro_export] macro_rules! log { ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { log::$level!( - target: crate::LOG_TARGET, + target: LOG_TARGET, concat!("[{:?}] 💰", $patter), >::block_number() $(, $values)* ) }; @@ -37,173 +60,157 @@ macro_rules! log { type PoolId = u32; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -type SharesOf = BalanceOf; -type SubPoolsWithEra = BoundedBTreeMap<::EraIndex, UnbondPool, MaxUnbonding>; - -/// Trait for communication with the staking pallet. -pub trait StakingInterface { - /// The minimum amount necessary to bond to be a nominator. This does not necessarily mean the - /// nomination will be counted in an election, but instead just enough to be stored as a - /// nominator (e.g. in the bags-list of polkadot) - fn minimum_bond() -> Balance; - - /// The current era for the elections system. - fn current_era() -> EraIndex; - - /// Wether or not the elections system has an ongoing election. If there is an ongoing election - /// it is assumed that any new pool joiner's funds will not start earning rewards until the - /// following era. - // TODO: a main advantage of our snapshot system is that we should not care about when an - // election is ongoing. I hope and predict that this won't be needed or is being use by mistake. - fn is_ongoing_election() -> bool; - - /// Balance `controller` has bonded for nominating. - fn bonded_balance(controller: &AccountId) -> Balance; +type PointsOf = BalanceOf; +type SubPoolsWithEra = BoundedBTreeMap, ::MaxUnbonding>; - fn bond_extra(controller: &AccountId, extra: Balance) -> DispatchResult; - - fn unbond(controller: &AccountId, value: Balance) -> DispatchResult; - - /// Number of eras that staked funds must remain bonded for. - fn bond_duration() -> EraIndex; -} +const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] +#[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct Delegator { pool: PoolId, - /// The quantity of shares this delegator has in the p - shares: SharesOf, + /// The quantity of points this delegator has in the primary pool or in an sub pool if + /// `Self::unbonding_era` is some. + points: PointsOf, /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. /// Assuming no massive burning events, we expect this value to always be below total issuance. + /// ^ double check the above is an OK assumption /// This value lines up with the `RewardPool.total_earnings` after a delegator claims a payout. - /// TODO ^ double check the above is an OK assumption reward_pool_total_earnings: BalanceOf, /// The era this delegator started unbonding at. - unbonding_era: Option, + unbonding_era: Option, } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] +#[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] -pub struct Pool { - shares: SharesOf, // Probably needs to be some type of BigUInt +pub struct PrimaryPool { + points: PointsOf, // Probably needs to be some type of BigUInt // The _Stash_ and _Controller_ account for the pool. account_id: T::AccountId, } -impl Pool { - /// Get the amount of shares to issue for some new funds that will be bonded in the pool. - /// - /// * `new_funds`: Incoming funds to be bonded against the pool. - fn shares_to_issue(&self, new_funds: BalanceOf) -> SharesOf { - // TODO: this is the great thing about having in storage items, you have access - // to lots of more things. +impl PrimaryPool { + /// Get the amount of points to issue for some new funds that will be bonded in the pool. + fn points_to_issue(&self, new_funds: BalanceOf) -> PointsOf { let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); - if bonded_balance.is_zero() || self.shares.is_zero() { - debug_assert!(bonded_balance.is_zero() && self.shares.is_zero()); + if bonded_balance.is_zero() || self.points.is_zero() { + debug_assert!(bonded_balance.is_zero() && self.points.is_zero()); - // all pools start with a 1:1 ratio of balance:shares - new_funds + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) } else { - let shares_per_balance = { + let points_per_balance = { let balance = T::BalanceToU128::convert(bonded_balance); - let shares = T::BalanceToU128::convert(self.shares); - FixedU128::saturating_from_rational(shares, balance) + let points = T::BalanceToU128::convert(self.points); + // REMINDER: `saturating_from_rational` panics if denominator is zero + FixedU128::saturating_from_rational(points, balance) }; let new_funds = T::BalanceToU128::convert(new_funds); - T::U128ToBalance::convert(shares_per_balance.saturating_mul_int(new_funds)) + T::U128ToBalance::convert(points_per_balance.saturating_mul_int(new_funds)) } } - /// TODO: doc seems wrong, this just converts shares -> balance, not actually updating anything. - /// - /// Based on the given shares, unbond the equivalent balance, update the pool accordingly, and - /// return the balance unbonded. - fn balance_to_unbond(&self, delegator_shares: SharesOf) -> BalanceOf { + // Get the amount of balance to unbond from the pool based on a delegator's points of the pool. + fn balance_to_unbond(&self, delegator_points: PointsOf) -> BalanceOf { let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); - if bonded_balance.is_zero() || delegator_shares.is_zero() { + if bonded_balance.is_zero() || delegator_points.is_zero() { // There is nothing to unbond return Zero::zero() } let balance_per_share = { let balance = T::BalanceToU128::convert(bonded_balance); - let shares = T::BalanceToU128::convert(self.shares); - FixedU128::saturating_from_rational(balance, shares) + let points = T::BalanceToU128::convert(self.points); + // REMINDER: `saturating_from_rational` panics if denominator is zero + FixedU128::saturating_from_rational(balance, points) }; - let delegator_shares = T::BalanceToU128::convert(delegator_shares); + let delegator_points = T::BalanceToU128::convert(delegator_points); - T::U128ToBalance::convert(balance_per_share.saturating_mul_int(delegator_shares)) + T::U128ToBalance::convert(balance_per_share.saturating_mul_int(delegator_points)) } } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] +#[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct RewardPool { - // TODO look into using the BigUInt /// The balance of this reward pool after the last claimed payout. balance: BalanceOf, - /// The shares of this reward pool after the last claimed payout - shares: BalanceOf, // TODO maybe MaxEncodedLen or something + /// The points of this reward pool after the last claimed payout + points: BalanceOf, /// The total earnings _ever_ of this reward pool after the last claimed payout. I.E. the sum - /// of all incoming balance. + /// of all incoming balance through the pools life. total_earnings: BalanceOf, /// The reward destination for the pool. account_id: T::AccountId, } -// TODO: the default or other derives don't work because the default derive will only be valid when -// all the generics are also `Default`. In this case `T: Default` does not hold and therefore does -// not work. For this, we have various `NoBound` derives in support. +impl RewardPool { + fn update_total_earnings_and_balance(mut self) -> Self { + let current_balance = T::Currency::free_balance(&self.account_id); + // The earnings since the last time it was updated + let new_earnings = current_balance.saturating_sub(self.balance); + // The lifetime earnings of the of the reward pool + self.total_earnings = new_earnings.saturating_add(self.total_earnings); + self.balance = current_balance; + + self + } +} + #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound)] +#[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] struct UnbondPool { - shares: SharesOf, + points: PointsOf, balance: BalanceOf, } impl UnbondPool { - fn shares_to_issue(&self, new_funds: BalanceOf) -> SharesOf { - if self.balance.is_zero() || self.shares.is_zero() { - debug_assert!(self.balance.is_zero() && self.shares.is_zero()); + fn points_to_issue(&self, new_funds: BalanceOf) -> PointsOf { + if self.balance.is_zero() || self.points.is_zero() { + debug_assert!(self.balance.is_zero() && self.points.is_zero()); - // all pools start with a 1:1 ratio of balance:shares - new_funds + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) } else { - let shares_per_balance = { + let points_per_balance = { let balance = T::BalanceToU128::convert(self.balance); - let shares = T::BalanceToU128::convert(self.shares); - FixedU128::saturating_from_rational(shares, balance) + let points = T::BalanceToU128::convert(self.points); + // REMINDER: `saturating_from_rational` panics if denominator is zero + FixedU128::saturating_from_rational(points, balance) }; let new_funds = T::BalanceToU128::convert(new_funds); - T::U128ToBalance::convert(shares_per_balance.saturating_mul_int(new_funds)) + T::U128ToBalance::convert(points_per_balance.saturating_mul_int(new_funds)) } } - fn balance_to_unbond(&self, delegator_shares: SharesOf) -> BalanceOf { - if self.balance.is_zero() || delegator_shares.is_zero() { + fn balance_to_unbond(&self, delegator_points: PointsOf) -> BalanceOf { + if self.balance.is_zero() || delegator_points.is_zero() { // There is nothing to unbond return Zero::zero() } let balance_per_share = { let balance = T::BalanceToU128::convert(self.balance); - let shares = T::BalanceToU128::convert(self.shares); - FixedU128::saturating_from_rational(balance, shares) + let points = T::BalanceToU128::convert(self.points); + // REMINDER: `saturating_from_rational` panics if denominator is zero + FixedU128::saturating_from_rational(balance, points) }; - let delegator_shares = T::BalanceToU128::convert(delegator_shares); + let delegator_points = T::BalanceToU128::convert(delegator_points); - T::U128ToBalance::convert(balance_per_share.saturating_mul_int(delegator_shares)) + T::U128ToBalance::convert(balance_per_share.saturating_mul_int(delegator_points)) } } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound)] +#[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] struct SubPoolsContainer { @@ -218,8 +225,8 @@ struct SubPoolsContainer { impl SubPoolsContainer { /// Merge the oldest unbonding pool with an era into the general unbond pool with no associated /// era. - fn maybe_merge_pools(mut self, current_era: T::EraIndex) -> Self { - if current_era < T::MAX_UNBONDING.into() { + fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self { + if current_era < T::MaxUnbonding::get().into() { // For the first `T::MAX_UNBONDING` eras of the chain we don't need to do anything. // I.E. if `MAX_UNBONDING` is 5 and we are in era 4 we can add a pool for this era and // have exactly `MAX_UNBONDING` pools. @@ -227,16 +234,16 @@ impl SubPoolsContainer { } // I.E. if `MAX_UNBONDING` is 5 and current era is 10, we only want to retain pools 6..=10. - let oldest_era_to_keep = current_era - (T::MAX_UNBONDING.saturating_add(1)).into(); + let oldest_era_to_keep = current_era - T::MaxUnbonding::get().saturating_add(1); let eras_to_remove: Vec<_> = self.with_era.keys().cloned().filter(|era| *era < oldest_era_to_keep).collect(); for era in eras_to_remove { if let Some(p) = self.with_era.remove(&era) { - self.no_era.shares.saturating_add(p.shares); + self.no_era.points.saturating_add(p.points); self.no_era.balance.saturating_add(p.balance); } else { - // lol + // the world is broken } } @@ -244,17 +251,6 @@ impl SubPoolsContainer { } } -// Wrapper for `T::MAX_UNBONDING` to satisfy `trait Get`. -// TODO: my 2 cents on making life harder for yourself with the `const` and manually wrapping it in -// Get: just don't, not worth the effort and you will probably regret it when wanting to make these -// values dynamic in tests. -pub struct MaxUnbonding(PhantomData); -impl Get for MaxUnbonding { - fn get() -> u32 { - T::MAX_UNBONDING - } -} - #[frame_support::pallet] pub mod pallet { use super::*; @@ -282,35 +278,18 @@ pub mod pallet { // Infallible method for converting `u128` to `Currency::Balance`. type U128ToBalance: Convert>; - // TODO: This should 200% be an associated type of the `StakingInterface`. - // also, is EraIndex really generic anywhere? I thought it is pretty hardcoded to be u32 - // somewhere in `sp-staking`. We could move it there and make everything easier. (again, - // this can be a backport PR to master ahead of time) - - /// The type for unique era indexes. Likely comes from what implements `StakingInterface`. - type EraIndex: Member - + Parameter - + AtLeast32BitUnsigned - + Default - + Copy - + MaybeSerializeDeserialize - + MaxEncodedLen - + TypeInfo; - - // TODO: learning from my mistakes, you probably will have an easier time if you make all of - // these be associated types and just constrain them here, so you won't need name it when - // you do fully-qualified syntax e.g. `type StakingInterface: StakingInterface, AccountId = Self::AccountId, EraIndex = Self::EraIndex>;` - /// The interface for nominating. - type StakingInterface: StakingInterface, Self::AccountId, Self::EraIndex>; + type StakingInterface: StakingInterface< + Balance = BalanceOf, + AccountId = Self::AccountId, + LookupSource = ::Source, + >; /// The maximum amount of eras an unbonding pool can exist prior to being merged with the - /// "average" (TODO need better terminology) unbonding pool. - // TODO: kian yelling don't do this to yourself :D - const MAX_UNBONDING: u32; - - // MaxPools + /// `no_era pool. This should at least be greater then the `UnbondingDuration` for staking + /// so delegator have a chance to withdraw unbonded before their pool gets merged with the + /// `no_era` pool. This *must* at least be greater then the slash deffer duration. + type MaxUnbonding: Get; } /// Active delegators. @@ -318,9 +297,15 @@ pub mod pallet { pub(crate) type Delegators = CountedStorageMap<_, Twox64Concat, T::AccountId, Delegator>; + /// `PoolId` lookup from the pool's `AccountId`. Useful for pool lookup from the slashing + /// system. + #[pallet::storage] + pub(crate) type PoolIds = CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId>; + /// Bonded pools. #[pallet::storage] - pub(crate) type PrimaryPools = CountedStorageMap<_, Twox64Concat, PoolId, Pool>; + pub(crate) type PrimaryPools = + CountedStorageMap<_, Twox64Concat, PoolId, PrimaryPool>; /// Reward pools. This is where there rewards for each pool accumulate. When a delegators payout /// is claimed, the balance comes out fo the reward pool. @@ -337,10 +322,10 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - // TODO: these operations are per delegator - so these events could become decently noisy - // if things scale a lot - consider not including these. Joined { delegator: T::AccountId, pool: PoolId, bonded: BalanceOf }, - Payout { delegator: T::AccountId, pool: PoolId, payout: BalanceOf }, + PaidOut { delegator: T::AccountId, pool: PoolId, payout: BalanceOf }, + Unbonded { delegator: T::AccountId, pool: PoolId, amount: BalanceOf }, + Withdrawn { delegator: T::AccountId, pool: PoolId, amount: BalanceOf }, } #[pallet::error] @@ -363,18 +348,27 @@ pub mod pallet { NotUnbonding, /// Unbonded funds cannot be withdrawn yet because the bond duration has not passed. NotUnbondedYet, + /// The given pool id cannot be used to create a new pool because it is already in use. + IdInUse, + /// The amount does not meet the minimum bond to start nominating. + MinimiumBondNotMet, } #[pallet::call] impl Pallet { - /// Join a pre-existing pool. Note that an account can only be a member of a single pool. + /// Join a pre-existing pool. + /// + /// Notes + /// * an account can only be a member of a single pool. + /// * this will *not* dust the delegator account, so the delegator must have at least + /// `existential deposit + amount` in their account. #[pallet::weight(666)] pub fn join(origin: OriginFor, amount: BalanceOf, target: PoolId) -> DispatchResult { let who = ensure_signed(origin)?; // if a delegator already exists that means they already belong to a pool ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); - // Ensure that the `target` pool exists + // Ensure that the `target` pool exists, let mut primary_pool = PrimaryPools::::get(target).ok_or(Error::::PoolNotFound)?; // And that `amount` will meet the minimum bond @@ -383,42 +377,48 @@ pub mod pallet { old_free_balance.saturating_add(amount) >= T::StakingInterface::minimum_bond(), Error::::InsufficientBond ); + // Note that we don't actually care about writing the reward pool, we just need its + // total earnings at this point in time. + let reward_pool = RewardPools::::get(target) + .ok_or(Error::::RewardPoolNotFound)? + // This is important because we want the most up-to-date total earnings. + .update_total_earnings_and_balance(); // Transfer the funds to be bonded from `who` to the pools account so the pool can then // go bond them. - // Note importantly that we can't error after this transfer goes through. - // TODO I assume this does proper keep alive checks etc but need to double check T::Currency::transfer( &who, &primary_pool.account_id, amount, ExistenceRequirement::KeepAlive, )?; - // this should now include the transferred balance + // This should now include the transferred balance. let new_free_balance = T::Currency::free_balance(&primary_pool.account_id); - // we get the exact amount we can bond extra + // Get the exact amount we can bond extra. let exact_amount_to_bond = new_free_balance.saturating_sub(old_free_balance); - // issue the new shares - let new_shares = primary_pool.shares_to_issue(exact_amount_to_bond); - primary_pool.shares = primary_pool.shares.saturating_add(new_shares); + // Issue the new points. + let new_points = primary_pool.points_to_issue(exact_amount_to_bond); + primary_pool.points = primary_pool.points.saturating_add(new_points); let delegator = Delegator:: { pool: target, - shares: new_shares, - // TODO this likely needs to be the reward pools total earnings at this block - // - go and double check - reward_pool_total_earnings: Zero::zero(), + points: new_points, + // TODO double check that this is ok. + // At best the reward pool has the rewards up through the previous era. If the + // delegator joins prior to the snapshot they will benefit from the rewards of the + // current era despite not contributing to the pool's vote weight. If they join + // after the snapshot is taken they will benefit from the rewards of the next *2* + // eras because their vote weight will not be counted until the snapshot in current + // era + 1. + reward_pool_total_earnings: reward_pool.total_earnings, unbonding_era: None, }; - // Do bond extra T::StakingInterface::bond_extra(&primary_pool.account_id, exact_amount_to_bond)?; - // Write the pool and delegator to storage Delegators::insert(who.clone(), delegator); PrimaryPools::insert(target, primary_pool); - // And finally emit an event to confirm the exact amount bonded Self::deposit_event(Event::::Joined { delegator: who, pool: target, @@ -428,13 +428,11 @@ pub mod pallet { Ok(()) } - /// Claim a payout for a delegator can use this to claim their payout based on the - /// rewards /// that the pool has accumulated since their last claimed payout (OR since - /// - /// joining if this /// is there for). The payout will go to the delegators account. + /// A bonded delegator can use this to claim their payout based on the rewards that the pool + /// has accumulated since their last claimed payout (OR since joining if this is there first + /// time claiming rewards). /// - /// This extrinsic is permisionless in the sense that any account can call it for any - /// delegator in the system. + /// Note that the payout will go to the delegator's account. #[pallet::weight(666)] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -453,7 +451,8 @@ pub mod pallet { Ok(()) } - /// Unbond _all_ funds. + /// A bonded delegator can use this to unbond _all_ funds from the pool. + /// In order to withdraw the funds, the delegator must wait #[pallet::weight(666)] pub fn unbond(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -461,7 +460,7 @@ pub mod pallet { let mut primary_pool = PrimaryPools::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; - // Claim the the payout prior to unbonding. Once the user is unbonding their shares + // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the primary pool and thus they can no longer claim their payouts. // It is not strictly necessary to claim the rewards, but we do it here for UX. Self::do_reward_payout(who.clone(), delegator, &primary_pool)?; @@ -470,18 +469,15 @@ pub mod pallet { let mut delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPools::::get(delegator.pool).unwrap_or_default(); - // TODO double check if we need to count for elections when - // the unbonding era. let current_era = T::StakingInterface::current_era(); - let balance_to_unbond = primary_pool.balance_to_unbond(delegator.shares); + let balance_to_unbond = primary_pool.balance_to_unbond(delegator.points); // Update the primary pool. Note that we must do this *after* calculating the balance - // to unbond. - primary_pool.shares = primary_pool.shares.saturating_sub(delegator.shares); + // to unbond so we have the correct points for the balance:share ratio. + primary_pool.points = primary_pool.points.saturating_sub(delegator.points); - // Unbond in the actual underlying pool - we can't fail after this - // TODO - we can only do this for as many locking chunks are accepted + // Unbond in the actual underlying pool T::StakingInterface::unbond(&primary_pool.account_id, balance_to_unbond)?; // Merge any older pools into the general, era agnostic unbond pool. Note that we do @@ -491,19 +487,22 @@ pub mod pallet { // Update the unbond pool associated with the current era with the // unbonded funds. Note that we lazily create the unbond pool if it // does not yet exist. - // let unbond_pool = sub_pools - // .with_era - // .entry(current_era) - // .or_insert_with(|| UnbondPool::::default()); { - let unbond_pool = sub_pools.with_era.get_mut(¤t_era).unwrap(); - let shares_to_issue = unbond_pool.shares_to_issue(balance_to_unbond); - unbond_pool.shares = unbond_pool.shares.saturating_add(shares_to_issue); + let mut unbond_pool = + sub_pools.with_era.entry(current_era).or_insert_with(|| UnbondPool::default()); + let points_to_issue = unbond_pool.points_to_issue(balance_to_unbond); + unbond_pool.points = unbond_pool.points.saturating_add(points_to_issue); unbond_pool.balance = unbond_pool.balance.saturating_add(balance_to_unbond); } delegator.unbonding_era = Some(current_era); + Self::deposit_event(Event::::Unbonded { + delegator: who.clone(), + pool: delegator.pool, + amount: balance_to_unbond, + }); + // Now that we know everything has worked write the items to storage. PrimaryPools::insert(delegator.pool, primary_pool); SubPools::insert(delegator.pool, sub_pools); @@ -519,23 +518,23 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); - if current_era.saturating_sub(unbonding_era) < T::StakingInterface::bond_duration() { + if current_era.saturating_sub(unbonding_era) < T::StakingInterface::bonding_duration() { return Err(Error::::NotUnbondedYet.into()) }; let mut sub_pools = SubPools::::get(delegator.pool).unwrap_or_default(); let balance_to_unbond = if let Some(pool) = sub_pools.with_era.get_mut(¤t_era) { - let balance_to_unbond = pool.balance_to_unbond(delegator.shares); - pool.shares = pool.shares.saturating_sub(delegator.shares); + let balance_to_unbond = pool.balance_to_unbond(delegator.points); + pool.points = pool.points.saturating_sub(delegator.points); pool.balance = pool.balance.saturating_sub(balance_to_unbond); balance_to_unbond } else { // A pool does not belong to this era, so it must have been merged to the era-less // pool. - let balance_to_unbond = sub_pools.no_era.balance_to_unbond(delegator.shares); - sub_pools.no_era.shares = sub_pools.no_era.shares.saturating_sub(delegator.shares); + let balance_to_unbond = sub_pools.no_era.balance_to_unbond(delegator.points); + sub_pools.no_era.points = sub_pools.no_era.points.saturating_sub(delegator.points); sub_pools.no_era.balance = sub_pools.no_era.balance.saturating_sub(balance_to_unbond); @@ -553,63 +552,153 @@ pub mod pallet { SubPools::::insert(delegator.pool, sub_pools); + Self::deposit_event(Event::::Withdrawn { + delegator: who, + pool: delegator.pool, + amount: balance_to_unbond, + }); + + Ok(()) + } + + #[pallet::weight(666)] + pub fn create( + origin: OriginFor, + id: PoolId, + targets: Vec<::Source>, + amount: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!(!PrimaryPools::::contains_key(id), Error::::IdInUse); + ensure!(amount >= T::StakingInterface::minimum_bond(), Error::::MinimiumBondNotMet); + + let (stash, reward_dest) = Self::create_accounts(&who, id); + + T::Currency::transfer(&who, &stash, amount, ExistenceRequirement::AllowDeath)?; + + // T::StakingInterface::can_bond() + // T::StakingInterface::can_nominate() + + T::StakingInterface::bond( + stash.clone(), + // We make the stash and controller the same for simplicity + stash.clone(), + amount, + reward_dest.clone(), + )?; + + T::StakingInterface::nominate(stash.clone(), targets)?; + + let mut primary_pool = PrimaryPool:: { points: Zero::zero(), account_id: stash }; + let points_to_issue = primary_pool.points_to_issue(amount); + primary_pool.points = points_to_issue; + + Delegators::::insert( + who, + Delegator:: { + pool: id, + points: points_to_issue, + reward_pool_total_earnings: Zero::zero(), + unbonding_era: None, + }, + ); + PrimaryPools::::insert(id, primary_pool); + RewardPools::::insert( + id, + RewardPool:: { + balance: Zero::zero(), + points: Zero::zero(), + total_earnings: Zero::zero(), + account_id: reward_dest, + }, + ); + Ok(()) } } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + assert!( + T::StakingInterface::bonding_duration() < T::MaxUnbonding::get(), + "There must be more unbonding pools then the bonding duration / + so a slash can be applied to relevant unboding pools. (We assume / + the bonding duration > slash deffer duration.", + ); + } + } } impl Pallet { + fn create_accounts(who: &T::AccountId, id: PoolId) -> (T::AccountId, T::AccountId) { + let parent_hash = frame_system::Pallet::::parent_hash(); + let ext_index = frame_system::Pallet::::extrinsic_index().unwrap_or_default(); + + let stash_entropy = + (b"pools/stash", who, id, parent_hash, ext_index).using_encoded(sp_core::blake2_256); + let reward_entropy = + (b"pools/rewards", who, id, parent_hash, ext_index).using_encoded(sp_core::blake2_256); + + ( + Decode::decode(&mut TrailingZeroInput::new(stash_entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed"), + Decode::decode(&mut TrailingZeroInput::new(reward_entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed"), + ) + } + /// Calculate the rewards for `delegator`. fn calculate_delegator_payout( - primary_pool: &Pool, - mut reward_pool: RewardPool, + primary_pool: &PrimaryPool, + reward_pool: RewardPool, mut delegator: Delegator, ) -> Result<(RewardPool, Delegator, BalanceOf), DispatchError> { // If the delegator is unbonding they cannot claim rewards. Note that when the delagator // goes to unbond, the unbond function should claim rewards for the final time. ensure!(delegator.unbonding_era.is_none(), Error::::AlreadyUnbonding); - let current_balance = T::Currency::free_balance(&reward_pool.account_id); - - // The earnings since the last claimed payout - let new_earnings = current_balance.saturating_sub(reward_pool.balance); - - // The lifetime earnings of the of the reward pool - let new_total_earnings = new_earnings.saturating_add(reward_pool.total_earnings); + let last_total_earnings = reward_pool.total_earnings; + let mut reward_pool = reward_pool.update_total_earnings_and_balance(); + let new_earnings = reward_pool.total_earnings.saturating_sub(last_total_earnings); - // The new shares that will be added to the pool. For every unit of balance that has - // been earned by the reward pool, we inflate the reward pool shares by - // `primary_pool.total_shares`. In effect this allows each, single unit of balance (e.g. - // plank) to be divvied up pro-rata among delegators based on shares. - // TODO this needs to be some sort of BigUInt arithmetic - let new_shares = primary_pool.shares.saturating_mul(new_earnings); + // The new points that will be added to the pool. For every unit of balance that has + // been earned by the reward pool, we inflate the reward pool points by + // `primary_pool.total_points`. In effect this allows each, single unit of balance (e.g. + // plank) to be divvied up pro-rata among delegators based on points. + // TODO this needs to be some sort of BigUInt arithmetic + let new_points = primary_pool.points.saturating_mul(new_earnings); - // The shares of the reward pool after taking into account the new earnings - let current_shares = reward_pool.shares.saturating_add(new_shares); + // The points of the reward pool after taking into account the new earnings. Notice that + // this always increases over time except for when we subtract delegator virtual shares + let current_points = reward_pool.points.saturating_add(new_points); // The rewards pool's earnings since the last time this delegator claimed a payout let new_earnings_since_last_claim = - new_total_earnings.saturating_sub(delegator.reward_pool_total_earnings); - // The shares of the reward pool that belong to the delegator. - let delegator_virtual_shares = - delegator.shares.saturating_mul(new_earnings_since_last_claim); - - let delegator_payout = { - let delegator_ratio_of_shares = FixedU128::saturating_from_rational( - T::BalanceToU128::convert(delegator_virtual_shares), - T::BalanceToU128::convert(current_shares), + reward_pool.total_earnings.saturating_sub(delegator.reward_pool_total_earnings); + // The points of the reward pool that belong to the delegator. + let delegator_virtual_points = + delegator.points.saturating_mul(new_earnings_since_last_claim); + + let delegator_payout = if delegator_virtual_points.is_zero() || current_points.is_zero() { + BalanceOf::::zero() + } else { + // REMINDER: `saturating_from_rational` panics if denominator is zero + let delegator_ratio_of_points = FixedU128::saturating_from_rational( + T::BalanceToU128::convert(delegator_virtual_points), + T::BalanceToU128::convert(current_points), ); - let payout = delegator_ratio_of_shares - .saturating_mul_int(T::BalanceToU128::convert(current_balance)); + let payout = delegator_ratio_of_points + .saturating_mul_int(T::BalanceToU128::convert(reward_pool.balance)); T::U128ToBalance::convert(payout) }; // Record updates - delegator.reward_pool_total_earnings = new_total_earnings; - reward_pool.shares = current_shares.saturating_sub(delegator_virtual_shares); - reward_pool.balance = current_balance; - reward_pool.total_earnings = new_total_earnings; + delegator.reward_pool_total_earnings = reward_pool.total_earnings; + reward_pool.points = current_points.saturating_sub(delegator_virtual_points); + reward_pool.balance = reward_pool.balance.saturating_sub(delegator_payout); Ok((reward_pool, delegator, delegator_payout)) } @@ -621,15 +710,8 @@ impl Pallet { pool: PoolId, payout: BalanceOf, ) -> Result<(), DispatchError> { - T::Currency::transfer( - reward_pool, - &delegator, - payout, - // TODO double check we are ok with dusting the account - If their is a very high - // ED this could lead to a non-negligible loss of rewards - ExistenceRequirement::AllowDeath, // Dust may be lost here - )?; - Self::deposit_event(Event::::Payout { delegator, pool, payout }); + T::Currency::transfer(reward_pool, &delegator, payout, ExistenceRequirement::AllowDeath)?; + Self::deposit_event(Event::::PaidOut { delegator, pool, payout }); Ok(()) } @@ -637,7 +719,7 @@ impl Pallet { fn do_reward_payout( delegator_id: T::AccountId, delegator: Delegator, - primary_pool: &Pool, + primary_pool: &PrimaryPool, ) -> DispatchResult { let reward_pool = RewardPools::::get(&delegator.pool).ok_or_else(|| { log!(error, "A reward pool could not be found, this is a system logic error."); @@ -662,11 +744,101 @@ impl Pallet { Ok(()) } + + fn do_slash( + // This would be the nominator account + pool_account: &T::AccountId, + // Value of slash + slash_amount: BalanceOf, + // Era the slash was initially reported + slash_era: EraIndex, + // Era the slash is applied in + apply_era: EraIndex, + ) -> Option<(BalanceOf, BTreeMap>)> { + let pool_id = PoolIds::::get(pool_account)?; + let mut sub_pools = SubPools::::get(pool_id).unwrap_or_default(); + + // TODO double check why we do slash_era + 1 + let affected_range = (slash_era + 1)..=apply_era; + + let bonded_balance = T::StakingInterface::bonded_balance(pool_account); + + // Note that this doesn't count the balance in the `no_era` pool + let unbonding_affected_balance: BalanceOf = + affected_range.clone().fold(BalanceOf::::zero(), |balance_sum, era| { + if let Some(unbond_pool) = sub_pools.with_era.get(&era) { + balance_sum.saturating_add(unbond_pool.balance) + } else { + balance_sum + } + }); + let total_affected_balance = bonded_balance.saturating_add(unbonding_affected_balance); + + if slash_amount < total_affected_balance { + // TODO this shouldn't happen as long as MaxBonding pools is greater thant the slash + // defer duration, which it should implicitly be because we expect it be longer then the + // UnbondindDuration. TODO clearly document these assumptions + }; + + // Panics if denominator is zero + let slash_ratio = if total_affected_balance <= Zero::zero() { + return Some((Zero::zero(), Default::default())) + } else { + // REMINDER: `saturating_from_rational` panics if denominator is zero + FixedU128::saturating_from_rational( + T::BalanceToU128::convert(slash_amount), + T::BalanceToU128::convert(total_affected_balance), + ) + }; + + let slash_multiplier = FixedU128::one().saturating_sub(slash_ratio); + + let unlock_chunk_balances: BTreeMap<_, _> = affected_range + .filter_map(|era| { + if let Some(mut unbond_pool) = sub_pools.with_era.get_mut(&era) { + let pre_slash_balance = T::BalanceToU128::convert(unbond_pool.balance); + let after_slash_balance = T::U128ToBalance::convert( + slash_multiplier.saturating_mul_int(pre_slash_balance), + ); + unbond_pool.balance = after_slash_balance; + + Some((era, after_slash_balance)) + } else { + None + } + }) + .collect(); + + SubPools::::insert(pool_id, sub_pools); + + let slashed_bonded_pool_balance = { + let pre_slash_balance = T::BalanceToU128::convert(bonded_balance); + T::U128ToBalance::convert(slash_multiplier.saturating_mul_int(pre_slash_balance)) + }; + + Some((slashed_bonded_pool_balance, unlock_chunk_balances)) + } } -// TODO -// - rebond_rewards +impl PoolsInterface for Pallet { + type AccountId = T::AccountId; + type Balance = BalanceOf; + + fn slash_pool( + pool_account: &Self::AccountId, + slash_amount: Self::Balance, + slash_era: EraIndex, + apply_era: EraIndex, + ) -> Option<(Self::Balance, BTreeMap)> { + Self::do_slash(pool_account, slash_amount, slash_era, apply_era) + } +} + +// +// - slashing +// - tests // - force pool creation +// - rebond_rewards // - force pool update // - force pool delete? diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs new file mode 100644 index 0000000000000..c9b47da6e679f --- /dev/null +++ b/frame/pools/src/mock.rs @@ -0,0 +1,184 @@ +use super::*; +use crate::{self as pools}; +use frame_support::{assert_ok, parameter_types}; +use frame_system::RawOrigin; + +pub type AccountId = u32; +pub type Balance = u32; + +parameter_types! { + static CurrentEra: EraIndex = 0; + static BondedBalance: Balance = 0; +} + +pub struct StakingMock; +impl sp_staking::StakingInterface for StakingMock { + type Balance = Balance; + type AccountId = AccountId; + type LookupSource = Self::AccountId; + + fn minimum_bond() -> Self::Balance { + 10 + } + + fn current_era() -> EraIndex { + CurrentEra::get() + } + + fn bonding_duration() -> EraIndex { + 3 + } + + fn bonded_balance(_: &Self::AccountId) -> Self::Balance { + BondedBalance::get() + } + + fn bond_extra(_: &Self::AccountId, _: Self::Balance) -> DispatchResult { + Ok(()) + } + + fn unbond(_: &Self::AccountId, _: Self::Balance) -> DispatchResult { + Ok(()) + } + + fn bond( + _: Self::AccountId, + _: Self::AccountId, + _: Self::Balance, + _: Self::AccountId, + ) -> DispatchResult { + Ok(()) + } + + fn nominate(_: Self::AccountId, _: Vec) -> DispatchResult { + Ok(()) + } +} + +impl frame_system::Config for Runtime { + type SS58Prefix = (); + type BaseCallFilter = frame_support::traits::Everything; + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Call = Call; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = sp_runtime::testing::Header; + type Event = Event; + type BlockHashCount = (); + type DbWeight = (); + type BlockLength = (); + type BlockWeights = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub static ExistentialDeposit: Balance = 1; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = frame_support::traits::ConstU32<1024>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +parameter_types! { + pub static MaxUnbonding: u32 = 5; +} + +pub struct BalanceToU128; +impl Convert for BalanceToU128 { + fn convert(n: Balance) -> u128 { + n as u128 + } +} + +pub struct U128ToBalance; +impl Convert for U128ToBalance { + fn convert(n: u128) -> Balance { + if n > Balance::MAX as u128 { + Balance::MAX + } else { + n as Balance + } + } +} + +impl pools::Config for Runtime { + type Event = Event; + type Currency = Balances; + type BalanceToU128 = BalanceToU128; + type U128ToBalance = U128ToBalance; + type StakingInterface = StakingMock; + type MaxUnbonding = MaxUnbonding; +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Event, Config}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Pools: pools::{Pallet, Call, Storage, Event}, + } +); + +#[derive(Default)] +pub struct ExtBuilder { + delegators: Vec<(AccountId, PoolId, Balance)>, +} + +impl ExtBuilder { + pub(crate) fn add_delegators(mut self, delegators: Vec<(AccountId, PoolId, Balance)>) -> Self { + self.delegators = delegators; + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let mut ext = sp_io::TestExternalities::from(storage); + + ext.execute_with(|| { + // make a pool + let amount_to_bond = ::StakingInterface::minimum_bond(); + Balances::make_free_balance_be(&10, amount_to_bond * 2); + + assert_ok!(Pools::create(RawOrigin::Signed(10).into(), 0, vec![100], amount_to_bond)); + for (account_id, pool_id, bonded) in self.delegators { + Balances::make_free_balance_be(&account_id, bonded * 2); + assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, pool_id)); + } + }); + + ext + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + self.build().execute_with(|| { + test(); + // post-checks can be added here + }) + } +} diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs new file mode 100644 index 0000000000000..61feba6b8ebea --- /dev/null +++ b/frame/pools/src/tests.rs @@ -0,0 +1,167 @@ +//! Generally for pool ids we use 0-9 and delegator ids 10-99. + +use super::*; +use crate::mock::{Balances, ExtBuilder, Pools, Runtime}; +use frame_support::assert_noop; + +// Pool 0's primary account id (i.e. its stash and controller account). +const PRIMARY_ACCOUNT: u32 = 1382160961; +// Pool 0's reward destination. +const REWARDS_ACCOUNT: u32 = 2625016280; + +#[test] +fn test_setup_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(PrimaryPools::::count(), 1); + assert_eq!(RewardPools::::count(), 1); + assert_eq!(SubPools::::count(), 0); + assert_eq!(Delegators::::count(), 1); + + assert_eq!( + PrimaryPools::::get(0).unwrap(), + PrimaryPool:: { points: 10, account_id: 1382160961 } + ); + assert_eq!( + RewardPools::::get(0).unwrap(), + RewardPool:: { + balance: 0, + points: 0, + total_earnings: 0, + account_id: 2625016280 + } + ); + assert_eq!( + Delegators::::get(10).unwrap(), + Delegator:: { + pool: 0, + points: 10, + reward_pool_total_earnings: 0, + unbonding_era: None + } + ) + }) +} + +mod primary_pool { + #[test] + fn points_to_issue_works() { + // zero case + } + + #[test] + fn balance_to_unbond_works() { + // zero case + } +} +mod reward_pool { + use super::*; + #[test] + fn update_total_earnings_and_balance_works() {} +} +mod unbond_pool { + #[test] + fn points_to_issue_works() { + // zero case + } + + #[test] + fn balance_to_unbond_works() { + // zero case + } +} +mod sub_pools_container {} + +mod join {} + +mod claim_payout { + use super::*; + + #[test] + fn calculate_delegator_payout_works_with_a_pool_of_1() { + ExtBuilder::default().build_and_execute(|| { + let rew = |balance, points, total_earnings| RewardPool:: { + balance, + points, + total_earnings, + account_id: REWARDS_ACCOUNT, + }; + let del = |reward_pool_total_earnings| Delegator:: { + pool: 0, + points: 10, + reward_pool_total_earnings, + unbonding_era: None, + }; + + let primary_pool = PrimaryPools::::get(0).unwrap(); + let reward_pool = RewardPools::::get(0).unwrap(); + let delegator = Delegators::::get(10).unwrap(); + + // given no rewards have been earned + // when + let (reward_pool, delegator, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator).unwrap(); + + // then + assert_eq!(payout, 0); + assert_eq!(delegator, del(0)); + assert_eq!(reward_pool, rew(0, 0, 0)); + + // given the pool has earned some rewards for the first time + Balances::make_free_balance_be(&REWARDS_ACCOUNT, 2); + + // when + let (reward_pool, delegator, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator).unwrap(); + + // then + assert_eq!(payout, 2); + assert_eq!(reward_pool, rew(0, 0, 2)); + assert_eq!(delegator, del(2)); + + // given the pool has earned rewards again + Balances::make_free_balance_be(&REWARDS_ACCOUNT, 4); + + // when + let (reward_pool, delegator, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator).unwrap(); + + // then + assert_eq!(payout, 4); + assert_eq!(reward_pool, rew(0, 0, 6)); + assert_eq!(delegator, del(6)); + + // given the pool has earned no new rewards + Balances::make_free_balance_be(&REWARDS_ACCOUNT, 0); + + // when + let (reward_pool, delegator, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator).unwrap(); + + // then + assert_eq!(payout, 0); + assert_eq!(reward_pool, rew(0, 0, 6)); + assert_eq!(delegator, del(6)); + }); + + // calculate_delegator_payout_errors_if_a_delegator_is_unbonding() { + // let primary_pool = PrimaryPools::::get(0); + // let reward_pool = RewardPools::::get(0); + // let mut delegator = Delegators::::get(10); + // delegator.unbonding_era = Some(0); + + // assert_noop!( + // calculate_delegator_payout(&primary_pool, reward_pool, delegator), + // ); + } +} + +mod unbond {} + +mod withdraw_unbonded {} + +mod create {} + +mod pools_interface { + #[test] + fn slash_pool_works() {} +} diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 268618fb5f44f..b6cf21ad35747 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -312,7 +312,7 @@ use sp_runtime::{ }; use sp_staking::{ offence::{Offence, OffenceError, ReportOffence}, - SessionIndex, + EraIndex, SessionIndex, }; use sp_std::{collections::btree_map::BTreeMap, convert::From, prelude::*}; pub use weights::WeightInfo; @@ -332,9 +332,6 @@ macro_rules! log { }; } -/// Counter for the number of eras that have passed. -pub type EraIndex = u32; - /// Counter for the number of "reward" points earned by a given validator. pub type RewardPoint = u32; @@ -527,14 +524,59 @@ impl StakingLedger where Balance: AtLeast32BitUnsigned + Saturating + Copy, + AccountId: Clone, { - /// Slash the validator for a given amount of balance. This can grow the value - /// of the slash in the case that the validator has less than `minimum_balance` + /// Slash the staker for a given amount of balance. This can grow the value + /// of the slash in the case that the staker has less than `minimum_balance` /// active funds. Returns the amount of funds actually slashed. /// /// Slashes from `active` funds first, and then `unlocking`, starting with the /// chunks that are closest to unlocking. - fn slash(&mut self, mut value: Balance, minimum_balance: Balance) -> Balance { + fn slash>( + &mut self, + value: Balance, + minimum_balance: Balance, + slash_era: EraIndex, + active_era: EraIndex, + ) -> Balance { + if let Some((new_active, new_chunk_balances)) = + P::slash_pool(&self.stash, value, slash_era, active_era) + { + self.pool_slash(new_active, new_chunk_balances) + } else { + self.standard_slash(value, minimum_balance) + } + } + + /// Slash a pool account + fn pool_slash( + &mut self, + new_active: Balance, + new_chunk_balances: BTreeMap, + ) -> Balance { + let mut total_slashed = Balance::zero(); + + // Modify the unlocking chunks in place + for chunk in &mut self.unlocking { + if let Some(new_balance) = new_chunk_balances.get(&chunk.era) { + let slashed_amount = chunk.value.saturating_sub(*new_balance); + self.total = self.total.saturating_sub(slashed_amount); + total_slashed = total_slashed.saturating_add(slashed_amount); + + chunk.value = *new_balance; + } + } + + // Update the actively bonded + let slashed_amount = self.active.saturating_sub(new_active); + self.total = self.total.saturating_sub(slashed_amount); + self.active = new_active; + + total_slashed.saturating_add(slashed_amount) + } + + // Slash a validator or nominator's stash + fn standard_slash(&mut self, mut value: Balance, minimum_balance: Balance) -> Balance { let pre_total = self.total; let total = &mut self.total; let active = &mut self.active; diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index c97541de81961..94e824f2364d4 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -37,13 +37,13 @@ use sp_runtime::{ }; use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - SessionIndex, + EraIndex, SessionIndex, }; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; use crate::{ - log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraIndex, EraPayout, Exposure, - ExposureOf, Forcing, IndividualExposure, Nominations, PositiveImbalanceOf, RewardDestination, + log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, Exposure, ExposureOf, + Forcing, IndividualExposure, Nominations, PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs, }; @@ -594,7 +594,7 @@ impl Pallet { for era in (*earliest)..keep_from { let era_slashes = ::UnappliedSlashes::take(&era); for slash in era_slashes { - slashing::apply_slash::(slash); + slashing::apply_slash::(slash, era, active_era); } } @@ -1210,7 +1210,7 @@ where unapplied.reporters = details.reporters.clone(); if slash_defer_duration == 0 { // Apply right away. - slashing::apply_slash::(unapplied); + slashing::apply_slash::(unapplied, slash_era, active_era); { let slash_cost = (6, 5); let reward_cost = (2, 2); diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index ad493fb168c6f..1cf6b56152a98 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -31,7 +31,7 @@ use sp_runtime::{ traits::{CheckedSub, SaturatedConversion, StaticLookup, Zero}, DispatchError, Perbill, Percent, }; -use sp_staking::SessionIndex; +use sp_staking::{EraIndex, PoolsInterface, SessionIndex}; use sp_std::{convert::From, prelude::*, result}; mod impls; @@ -39,7 +39,7 @@ mod impls; pub use impls::*; use crate::{ - log, migrations, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraIndex, EraPayout, + log, migrations, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, Releases, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, @@ -153,6 +153,8 @@ pub mod pallet { /// the bags-list is not desired, [`impls::UseNominatorsMap`] is likely the desired option. type SortedListProvider: SortedListProvider; + type PoolsInterface: PoolsInterface>; + /// Some parameters of the benchmarking. type BenchmarkingConfig: BenchmarkingConfig; @@ -931,23 +933,24 @@ pub mod pallet { ledger = ledger.consolidate_unlocked(current_era) } - let post_info_weight = - if ledger.unlocking.is_empty() && ledger.active < T::Currency::minimum_balance() { - // This account must have called `unbond()` with some value that caused the active - // portion to fall below existential deposit + will have no more unlocking chunks - // left. We can now safely remove all staking-related information. - Self::kill_stash(&stash, num_slashing_spans)?; - // Remove the lock. - T::Currency::remove_lock(STAKING_ID, &stash); - // This is worst case scenario, so we use the full weight and return None - None - } else { - // This was the consequence of a partial unbond. just update the ledger and move on. - Self::update_ledger(&controller, &ledger); + let post_info_weight = if ledger.unlocking.is_empty() && + ledger.active < T::Currency::minimum_balance() + { + // This account must have called `unbond()` with some value that caused the active + // portion to fall below existential deposit + will have no more unlocking chunks + // left. We can now safely remove all staking-related information. + Self::kill_stash(&stash, num_slashing_spans)?; + // Remove the lock. + T::Currency::remove_lock(STAKING_ID, &stash); + // This is worst case scenario, so we use the full weight and return None + None + } else { + // This was the consequence of a partial unbond. just update the ledger and move on. + Self::update_ledger(&controller, &ledger); - // This is only an update, so we use less overall weight. - Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) - }; + // This is only an update, so we use less overall weight. + Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) + }; // `old_total` should never be less than the new total because // `consolidate_unlocked` strictly subtracts balance. @@ -1473,8 +1476,8 @@ pub mod pallet { let _ = ensure_signed(origin)?; let ed = T::Currency::minimum_balance(); - let reapable = T::Currency::total_balance(&stash) < ed - || Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) + let reapable = T::Currency::total_balance(&stash) < ed || + Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) .map(|l| l.total) .unwrap_or_default() < ed; ensure!(reapable, Error::::FundedTarget); diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 4fa7d347da70d..22f867151c85d 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -50,8 +50,8 @@ //! Based on research at use crate::{ - BalanceOf, Config, EraIndex, Error, Exposure, NegativeImbalanceOf, Pallet, Perbill, - SessionInterface, Store, UnappliedSlash, + BalanceOf, Config, Error, Exposure, NegativeImbalanceOf, Pallet, Perbill, SessionInterface, + Store, UnappliedSlash, }; use codec::{Decode, Encode}; use frame_support::{ @@ -63,7 +63,7 @@ use sp_runtime::{ traits::{Saturating, Zero}, DispatchResult, RuntimeDebug, }; -use sp_staking::offence::DisableStrategy; +use sp_staking::{offence::DisableStrategy, EraIndex}; use sp_std::vec::Vec; /// The proportion of the slashing reward to be paid out on the first slashing detection. @@ -598,6 +598,8 @@ pub fn do_slash( value: BalanceOf, reward_payout: &mut BalanceOf, slashed_imbalance: &mut NegativeImbalanceOf, + slash_era: EraIndex, + apply_era: EraIndex, ) { let controller = match >::bonded(stash) { None => return, // defensive: should always exist. @@ -609,7 +611,12 @@ pub fn do_slash( None => return, // nothing to do. }; - let value = ledger.slash(value, T::Currency::minimum_balance()); + let value = ledger.slash::( + value, + T::Currency::minimum_balance(), + slash_era, + apply_era, + ); if !value.is_zero() { let (imbalance, missing) = T::Currency::slash(stash, value); @@ -628,7 +635,11 @@ pub fn do_slash( } /// Apply a previously-unapplied slash. -pub(crate) fn apply_slash(unapplied_slash: UnappliedSlash>) { +pub(crate) fn apply_slash( + unapplied_slash: UnappliedSlash>, + slash_era: EraIndex, + active_era: EraIndex, +) { let mut slashed_imbalance = NegativeImbalanceOf::::zero(); let mut reward_payout = unapplied_slash.payout; @@ -637,10 +648,19 @@ pub(crate) fn apply_slash(unapplied_slash: UnappliedSlash(&nominator, nominator_slash, &mut reward_payout, &mut slashed_imbalance); + do_slash::( + &nominator, + nominator_slash, + &mut reward_payout, + &mut slashed_imbalance, + slash_era, + active_era, + ); } pay_reporters::(reward_payout, slashed_imbalance, &unapplied_slash.reporters); diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index ed132adac657e..dcc0ec7c2fb17 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -23,8 +23,11 @@ use crate::{ }; use codec::{Decode, Encode, MaxEncodedLen}; use sp_std::{ - borrow::Borrow, collections::btree_map::BTreeMap, convert::TryFrom, marker::PhantomData, - ops::Deref, + borrow::Borrow, + collections::btree_map::BTreeMap, + convert::TryFrom, + marker::PhantomData, + ops::{Deref, DerefMut}, }; /// A bounded map based on a B-Tree. @@ -271,6 +274,15 @@ where } } +impl DerefMut for BoundedBTreeMap +where + K: Ord, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + impl AsRef> for BoundedBTreeMap where K: Ord, diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index e7db609e230aa..9ce1f7dcb1593 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -19,8 +19,63 @@ //! A crate which contains primitives that are useful for implementation that uses staking //! approaches in general. Definitions related to sessions, slashing, etc go here. +use sp_runtime::DispatchResult; +use sp_std::collections::btree_map::BTreeMap; pub mod offence; /// Simple index type with which we can count sessions. pub type SessionIndex = u32; + +/// Counter for the number of eras that have passed. +pub type EraIndex = u32; + +pub trait PoolsInterface { + type AccountId; + type Balance; + + fn slash_pool( + account_id: &Self::AccountId, + slash_amount: Self::Balance, + slash_era: EraIndex, + active_era: EraIndex, + ) -> Option<(Self::Balance, BTreeMap)>; +} + +/// Trait for communication with the staking pallet. +pub trait StakingInterface { + /// Balance type used by the staking system. + type Balance; + + /// AccountId type used by the staking system + type AccountId; + + type LookupSource; + + /// The minimum amount necessary to bond to be a nominator. This does not necessarily mean the + /// nomination will be counted in an election, but instead just enough to be stored as a + /// nominator (e.g. in the bags-list of polkadot) + fn minimum_bond() -> Self::Balance; + + /// Number of eras that staked funds must remain bonded for. + fn bonding_duration() -> EraIndex; + + /// The current era for the staking system. + fn current_era() -> EraIndex; + + /// Balance `controller` has bonded for nominating. + fn bonded_balance(controller: &Self::AccountId) -> Self::Balance; + + fn bond_extra(controller: &Self::AccountId, extra: Self::Balance) -> DispatchResult; + + fn unbond(controller: &Self::AccountId, value: Self::Balance) -> DispatchResult; + + fn bond( + stash: Self::AccountId, + controller: Self::AccountId, + amount: Self::Balance, + payee: Self::AccountId, + ) -> DispatchResult; + + fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult; +} From fb0bfcaa1f9b975cf89751c5916a96888b61b7b7 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 18 Jan 2022 18:40:59 -0800 Subject: [PATCH 013/299] Test: calculate_delegator_payout_errors_when_unbonding_era_is_some --- frame/pools/src/tests.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 61feba6b8ebea..6593c7419af24 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -2,7 +2,7 @@ use super::*; use crate::mock::{Balances, ExtBuilder, Pools, Runtime}; -use frame_support::assert_noop; +use frame_support::{assert_err, assert_noop}; // Pool 0's primary account id (i.e. its stash and controller account). const PRIMARY_ACCOUNT: u32 = 1382160961; @@ -153,6 +153,25 @@ mod claim_payout { // calculate_delegator_payout(&primary_pool, reward_pool, delegator), // ); } + + #[test] + fn calculate_delegator_payout_works_with_a_pool_of_3() {} + + #[test] + fn calculate_delegator_payout_errors_when_unbonding_era_is_some() { + ExtBuilder::default().build_and_execute(|| { + let primary_pool = PrimaryPools::::get(0).unwrap(); + let reward_pool = RewardPools::::get(0).unwrap(); + let mut delegator = Delegators::::get(10).unwrap(); + + delegator.unbonding_era = Some(0); + + assert_noop!( + Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator), + Error::::AlreadyUnbonding + ); + }) + } } mod unbond {} From 01c0d4b46a518a985559dd802c5459b748a50940 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Wed, 19 Jan 2022 12:58:03 -0800 Subject: [PATCH 014/299] Apply suggestions from code review Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/pools/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 1e3e3d57bcdf4..e30fc2d51c8d8 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -65,26 +65,26 @@ type SubPoolsWithEra = BoundedBTreeMap, const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] -#[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, frame_support::RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct Delegator { pool: PoolId, - /// The quantity of points this delegator has in the primary pool or in an sub pool if + /// The quantity of points this delegator has in the primary pool or in a sub pool if /// `Self::unbonding_era` is some. points: PointsOf, /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. /// Assuming no massive burning events, we expect this value to always be below total issuance. - /// ^ double check the above is an OK assumption + // TODO: ^ double check the above is an OK assumption /// This value lines up with the `RewardPool.total_earnings` after a delegator claims a payout. reward_pool_total_earnings: BalanceOf, /// The era this delegator started unbonding at. unbonding_era: Option, } -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] -#[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, frame_support::RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct PrimaryPool { @@ -134,8 +134,8 @@ impl PrimaryPool { } } -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] -#[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, frame_support::RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct RewardPool { From 79ca621ad76a32ff037bc0b0fb1e3b5a77ba7201 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 19 Jan 2022 15:25:49 -0800 Subject: [PATCH 015/299] Simplify create_accounts api --- frame/pools/src/lib.rs | 18 +++++++++--------- frame/pools/src/tests.rs | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index e30fc2d51c8d8..b311f000cd157 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -119,7 +119,7 @@ impl PrimaryPool { let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); if bonded_balance.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero() + return Zero::zero(); } let balance_per_share = { @@ -194,7 +194,7 @@ impl UnbondPool { fn balance_to_unbond(&self, delegator_points: PointsOf) -> BalanceOf { if self.balance.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero() + return Zero::zero(); } let balance_per_share = { @@ -230,7 +230,7 @@ impl SubPoolsContainer { // For the first `T::MAX_UNBONDING` eras of the chain we don't need to do anything. // I.E. if `MAX_UNBONDING` is 5 and we are in era 4 we can add a pool for this era and // have exactly `MAX_UNBONDING` pools. - return self + return self; } // I.E. if `MAX_UNBONDING` is 5 and current era is 10, we only want to retain pools 6..=10. @@ -519,7 +519,7 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); if current_era.saturating_sub(unbonding_era) < T::StakingInterface::bonding_duration() { - return Err(Error::::NotUnbondedYet.into()) + return Err(Error::::NotUnbondedYet.into()); }; let mut sub_pools = SubPools::::get(delegator.pool).unwrap_or_default(); @@ -573,7 +573,7 @@ pub mod pallet { ensure!(!PrimaryPools::::contains_key(id), Error::::IdInUse); ensure!(amount >= T::StakingInterface::minimum_bond(), Error::::MinimiumBondNotMet); - let (stash, reward_dest) = Self::create_accounts(&who, id); + let (stash, reward_dest) = Self::create_accounts(id); T::Currency::transfer(&who, &stash, amount, ExistenceRequirement::AllowDeath)?; @@ -632,14 +632,14 @@ pub mod pallet { } impl Pallet { - fn create_accounts(who: &T::AccountId, id: PoolId) -> (T::AccountId, T::AccountId) { + fn create_accounts(id: PoolId) -> (T::AccountId, T::AccountId) { let parent_hash = frame_system::Pallet::::parent_hash(); let ext_index = frame_system::Pallet::::extrinsic_index().unwrap_or_default(); let stash_entropy = - (b"pools/stash", who, id, parent_hash, ext_index).using_encoded(sp_core::blake2_256); + (b"pools/stash", id, parent_hash, ext_index).using_encoded(sp_core::blake2_256); let reward_entropy = - (b"pools/rewards", who, id, parent_hash, ext_index).using_encoded(sp_core::blake2_256); + (b"pools/rewards", id, parent_hash, ext_index).using_encoded(sp_core::blake2_256); ( Decode::decode(&mut TrailingZeroInput::new(stash_entropy.as_ref())) @@ -782,7 +782,7 @@ impl Pallet { // Panics if denominator is zero let slash_ratio = if total_affected_balance <= Zero::zero() { - return Some((Zero::zero(), Default::default())) + return Some((Zero::zero(), Default::default())); } else { // REMINDER: `saturating_from_rational` panics if denominator is zero FixedU128::saturating_from_rational( diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 6593c7419af24..c2790ec59bb21 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -5,9 +5,9 @@ use crate::mock::{Balances, ExtBuilder, Pools, Runtime}; use frame_support::{assert_err, assert_noop}; // Pool 0's primary account id (i.e. its stash and controller account). -const PRIMARY_ACCOUNT: u32 = 1382160961; +const PRIMARY_ACCOUNT: u32 = 2536596763; // Pool 0's reward destination. -const REWARDS_ACCOUNT: u32 = 2625016280; +const REWARDS_ACCOUNT: u32 = 736857005; #[test] fn test_setup_works() { @@ -19,7 +19,7 @@ fn test_setup_works() { assert_eq!( PrimaryPools::::get(0).unwrap(), - PrimaryPool:: { points: 10, account_id: 1382160961 } + PrimaryPool:: { points: 10, account_id: PRIMARY_ACCOUNT } ); assert_eq!( RewardPools::::get(0).unwrap(), @@ -27,7 +27,7 @@ fn test_setup_works() { balance: 0, points: 0, total_earnings: 0, - account_id: 2625016280 + account_id: REWARDS_ACCOUNT } ); assert_eq!( From d1a4124d7bfc07b57b753391bf22c4fe4585b332 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 19 Jan 2022 20:33:45 -0800 Subject: [PATCH 016/299] Test calculate_delegator_payout_works_with_a_pool_of_3; improve mock --- frame/pools/src/lib.rs | 30 +++-- frame/pools/src/mock.rs | 39 ++++-- frame/pools/src/tests.rs | 272 ++++++++++++++++++++++++++++++++------- 3 files changed, 275 insertions(+), 66 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index b311f000cd157..8dd3cbe7413d7 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -98,6 +98,9 @@ impl PrimaryPool { fn points_to_issue(&self, new_funds: BalanceOf) -> PointsOf { let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); if bonded_balance.is_zero() || self.points.is_zero() { + println!("bonded_balance={:?} points={:?}", bonded_balance, self.points); + // TODO this doesn't hold if the pool is totally slashed but we need some more logic for + // that case debug_assert!(bonded_balance.is_zero() && self.points.is_zero()); new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) @@ -175,6 +178,8 @@ struct UnbondPool { impl UnbondPool { fn points_to_issue(&self, new_funds: BalanceOf) -> PointsOf { if self.balance.is_zero() || self.points.is_zero() { + // TODO this doesn't hold if the pool is totally slashed but we need some more logic for + // that case debug_assert!(self.balance.is_zero() && self.points.is_zero()); new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) @@ -397,7 +402,8 @@ pub mod pallet { // Get the exact amount we can bond extra. let exact_amount_to_bond = new_free_balance.saturating_sub(old_free_balance); - // Issue the new points. + // We must calculate the points to issue *before* we bond `who`'s funds, else the + // points:balance ratio will be wrong. let new_points = primary_pool.points_to_issue(exact_amount_to_bond); primary_pool.points = primary_pool.points.saturating_add(new_points); let delegator = Delegator:: { @@ -572,13 +578,21 @@ pub mod pallet { ensure!(!PrimaryPools::::contains_key(id), Error::::IdInUse); ensure!(amount >= T::StakingInterface::minimum_bond(), Error::::MinimiumBondNotMet); + // TODO create can_* fns so we can bail in the beggining if some pre-conditions are not + // met T::StakingInterface::can_bond() + // T::StakingInterface::can_nominate() let (stash, reward_dest) = Self::create_accounts(id); T::Currency::transfer(&who, &stash, amount, ExistenceRequirement::AllowDeath)?; - // T::StakingInterface::can_bond() - // T::StakingInterface::can_nominate() + let mut primary_pool = + PrimaryPool:: { points: Zero::zero(), account_id: stash.clone() }; + + // We must calculate the points to issue *before* we bond who's funds, else + // points:balance ratio will be wrong. + let points_to_issue = primary_pool.points_to_issue(amount); + primary_pool.points = points_to_issue; T::StakingInterface::bond( stash.clone(), @@ -590,10 +604,6 @@ pub mod pallet { T::StakingInterface::nominate(stash.clone(), targets)?; - let mut primary_pool = PrimaryPool:: { points: Zero::zero(), account_id: stash }; - let points_to_issue = primary_pool.points_to_issue(amount); - primary_pool.points = points_to_issue; - Delegators::::insert( who, Delegator:: { @@ -661,6 +671,7 @@ impl Pallet { let last_total_earnings = reward_pool.total_earnings; let mut reward_pool = reward_pool.update_total_earnings_and_balance(); + // Notice there is an edge case where total_earnings have not increased and this is zero let new_earnings = reward_pool.total_earnings.saturating_sub(last_total_earnings); // The new points that will be added to the pool. For every unit of balance that has @@ -671,8 +682,9 @@ impl Pallet { let new_points = primary_pool.points.saturating_mul(new_earnings); // The points of the reward pool after taking into account the new earnings. Notice that - // this always increases over time except for when we subtract delegator virtual shares - let current_points = reward_pool.points.saturating_add(new_points); + // this only stays even or increases over time except for when we subtract delegator virtual + // shares. + let current_points = reward_pool.points.saturating_add(new_points); // 39,800 // The rewards pool's earnings since the last time this delegator claimed a payout let new_earnings_since_last_claim = diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index c9b47da6e679f..2e57ea3e219a3 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -6,12 +6,23 @@ use frame_system::RawOrigin; pub type AccountId = u32; pub type Balance = u32; +/// Pool 0's primary account id (i.e. its stash and controller account). +pub const PRIMARY_ACCOUNT: u32 = 2536596763; +/// Pool 0's reward destination. +pub const REWARDS_ACCOUNT: u32 = 736857005; + parameter_types! { static CurrentEra: EraIndex = 0; - static BondedBalance: Balance = 0; + pub static BondedBalanceMap: std::collections::HashMap = Default::default(); } pub struct StakingMock; +impl StakingMock { + fn set_bonded_balance(who: AccountId, bonded: Balance) { + BONDED_BALANCE_MAP.with(|m| m.borrow_mut().insert(who.clone(), bonded)); + } +} + impl sp_staking::StakingInterface for StakingMock { type Balance = Balance; type AccountId = AccountId; @@ -29,24 +40,28 @@ impl sp_staking::StakingInterface for StakingMock { 3 } - fn bonded_balance(_: &Self::AccountId) -> Self::Balance { - BondedBalance::get() + fn bonded_balance(who: &Self::AccountId) -> Self::Balance { + BondedBalanceMap::get().get(who).map(|v| *v).unwrap_or_default() } - fn bond_extra(_: &Self::AccountId, _: Self::Balance) -> DispatchResult { + fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { + // Simulate bond extra in `join` + BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(who).unwrap() += extra); Ok(()) } - fn unbond(_: &Self::AccountId, _: Self::Balance) -> DispatchResult { + fn unbond(who: &Self::AccountId, amount: Self::Balance) -> DispatchResult { + BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(who).unwrap() -= amount); Ok(()) } fn bond( + stash: Self::AccountId, _: Self::AccountId, - _: Self::AccountId, - _: Self::Balance, + amount: Self::Balance, _: Self::AccountId, ) -> DispatchResult { + StakingMock::set_bonded_balance(stash, amount); Ok(()) } @@ -145,11 +160,12 @@ frame_support::construct_runtime!( #[derive(Default)] pub struct ExtBuilder { - delegators: Vec<(AccountId, PoolId, Balance)>, + delegators: Vec<(AccountId, Balance)>, } impl ExtBuilder { - pub(crate) fn add_delegators(mut self, delegators: Vec<(AccountId, PoolId, Balance)>) -> Self { + // Add delegators to pool 0. + pub(crate) fn add_delegators(mut self, delegators: Vec<(AccountId, Balance)>) -> Self { self.delegators = delegators; self } @@ -166,9 +182,10 @@ impl ExtBuilder { Balances::make_free_balance_be(&10, amount_to_bond * 2); assert_ok!(Pools::create(RawOrigin::Signed(10).into(), 0, vec![100], amount_to_bond)); - for (account_id, pool_id, bonded) in self.delegators { + for (account_id, bonded) in self.delegators { Balances::make_free_balance_be(&account_id, bonded * 2); - assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, pool_id)); + + assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, 0)); } }); diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index c2790ec59bb21..38e1e4feb7a4a 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1,13 +1,8 @@ //! Generally for pool ids we use 0-9 and delegator ids 10-99. use super::*; -use crate::mock::{Balances, ExtBuilder, Pools, Runtime}; -use frame_support::{assert_err, assert_noop}; - -// Pool 0's primary account id (i.e. its stash and controller account). -const PRIMARY_ACCOUNT: u32 = 2536596763; -// Pool 0's reward destination. -const REWARDS_ACCOUNT: u32 = 736857005; +use crate::mock::{Balances, ExtBuilder, Pools, Runtime, PRIMARY_ACCOUNT, REWARDS_ACCOUNT}; +use frame_support::{assert_noop, assert_ok}; #[test] fn test_setup_works() { @@ -77,21 +72,35 @@ mod claim_payout { use super::*; #[test] - fn calculate_delegator_payout_works_with_a_pool_of_1() { + fn calculate_delegator_payout_errors_if_a_delegator_is_unbonding() { ExtBuilder::default().build_and_execute(|| { - let rew = |balance, points, total_earnings| RewardPool:: { - balance, - points, - total_earnings, - account_id: REWARDS_ACCOUNT, - }; - let del = |reward_pool_total_earnings| Delegator:: { - pool: 0, - points: 10, - reward_pool_total_earnings, - unbonding_era: None, - }; + let primary_pool = PrimaryPools::::get(0).unwrap(); + let reward_pool = RewardPools::::get(0).unwrap(); + let mut delegator = Delegators::::get(10).unwrap(); + delegator.unbonding_era = Some(0); + assert_noop!( + Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator), + Error::::AlreadyUnbonding + ); + }); + } + + #[test] + fn calculate_delegator_payout_works_with_a_pool_of_1() { + let rew = |balance, points, total_earnings| RewardPool:: { + balance, + points, + total_earnings, + account_id: REWARDS_ACCOUNT, + }; + let del = |reward_pool_total_earnings| Delegator:: { + pool: 0, + points: 10, + reward_pool_total_earnings, + unbonding_era: None, + }; + ExtBuilder::default().build_and_execute(|| { let primary_pool = PrimaryPools::::get(0).unwrap(); let reward_pool = RewardPools::::get(0).unwrap(); let delegator = Delegators::::get(10).unwrap(); @@ -142,35 +151,206 @@ mod claim_payout { assert_eq!(reward_pool, rew(0, 0, 6)); assert_eq!(delegator, del(6)); }); - - // calculate_delegator_payout_errors_if_a_delegator_is_unbonding() { - // let primary_pool = PrimaryPools::::get(0); - // let reward_pool = RewardPools::::get(0); - // let mut delegator = Delegators::::get(10); - // delegator.unbonding_era = Some(0); - - // assert_noop!( - // calculate_delegator_payout(&primary_pool, reward_pool, delegator), - // ); } #[test] - fn calculate_delegator_payout_works_with_a_pool_of_3() {} - - #[test] - fn calculate_delegator_payout_errors_when_unbonding_era_is_some() { - ExtBuilder::default().build_and_execute(|| { - let primary_pool = PrimaryPools::::get(0).unwrap(); - let reward_pool = RewardPools::::get(0).unwrap(); - let mut delegator = Delegators::::get(10).unwrap(); - - delegator.unbonding_era = Some(0); - - assert_noop!( - Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator), - Error::::AlreadyUnbonding - ); - }) + fn calculate_delegator_payout_works_with_a_pool_of_3() { + let rew = |balance, points, total_earnings| RewardPool:: { + balance, + points, + total_earnings, + account_id: REWARDS_ACCOUNT, + }; + let del = |points, reward_pool_total_earnings| Delegator:: { + pool: 0, + points, + reward_pool_total_earnings, + unbonding_era: None, + }; + + ExtBuilder::default() + .add_delegators(vec![(40, 40), (50, 50)]) + .build_and_execute(|| { + let mut primary_pool = PrimaryPools::::get(0).unwrap(); + primary_pool.points = 100; + + let reward_pool = RewardPools::::get(0).unwrap(); + // Delegator with 10 points + let del_10 = Delegators::::get(10).unwrap(); + // Delegator with 40 points + let del_40 = Delegators::::get(40).unwrap(); + // Delegator with 50 points + let del_50 = Delegators::::get(50).unwrap(); + + // Given we have a total of 100 points split among the delegators + assert_eq!(del_50.points + del_40.points + del_10.points, 100); + // and the reward pool has earned 100 in rewards + Balances::make_free_balance_be(&REWARDS_ACCOUNT, 100); + + // When + let (reward_pool, del_10, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_10).unwrap(); + + // Then + assert_eq!(payout, 10); // (10 del virtual points / 100 pool points) * 100 pool balance + assert_eq!(del_10, del(10, 100)); + assert_eq!(reward_pool, rew(90, 100 * 100 - 100 * 10, 100)); + // Mock the reward pool transferring the payout to del_10 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 10)); + + // When + let (reward_pool, del_40, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_40).unwrap(); + + // Then + // The exact math is (400/900) * 90, so ideally this should be 40. But given 400 / + // 900 (del virtual points / pool points) = ~0.444, it gets rounded down. + assert_eq!(payout, 39); + assert_eq!(del_40, del(40, 100)); + assert_eq!( + reward_pool, + rew( + 51, + 9_000 - 100 * 40, // old pool points - delegator virtual points + 100 + ) + ); + // Mock the reward pool transferring the payout to del_40 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 39)); + + // When + let (reward_pool, del_50, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_50).unwrap(); + + // Then + assert_eq!(payout, 51); // (50 del virtual points / 50 pool points) * 51 pool balance + assert_eq!(del_50, del(50, 100)); + assert_eq!(reward_pool, rew(0, 0, 100)); + // Mock the reward pool transferring the payout to del_50 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 51)); + + // Given the reward pool has some new rewards + Balances::make_free_balance_be(&REWARDS_ACCOUNT, 50); + + // When + let (reward_pool, del_10, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_10).unwrap(); + + // Then + assert_eq!(payout, 5); // (500 del virtual points / 5,000 pool points) * 50 pool balance + assert_eq!(del_10, del(10, 150)); + assert_eq!(reward_pool, rew(45, 5_000 - 50 * 10, 150)); + // Mock the reward pool transferring the payout to del_10 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 5)); + + // When + let (reward_pool, del_40, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_40).unwrap(); + + // Then + assert_eq!(payout, 19); // (2,000 del virtual points / 4,500 pool points) * 45 pool balance + assert_eq!(del_40, del(40, 150)); + assert_eq!(reward_pool, rew(26, 4_500 - 50 * 40, 150)); + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 19)); + + // Given del_50 hasn't claimed and the reward pools has just earned 50 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 50)); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 76); + + // When + let (reward_pool, del_50, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_50).unwrap(); + + // Then + assert_eq!(payout, 50); // (5,000 del virtual points / 7,5000 pool points) * 76 pool balance + assert_eq!(del_50, del(50, 200)); + assert_eq!( + reward_pool, + rew( + 26, + // old pool points + points from new earnings - del points. + // + // points from new earnings = new earnings(50) * primary_pool.points(100) + // del points = delegator.points(50) * new_earnings_since_last_claim (100) + (2_500 + 50 * 100) - 50 * 100, + 200, + ) + ); + // Mock the reward pool transferring the payout to del_50 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 50)); + + // When + let (reward_pool, del_10, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_10).unwrap(); + + // Then + assert_eq!(payout, 5); + assert_eq!(del_10, del(10, 200)); + assert_eq!(reward_pool, rew(21, 2_500 - 10 * 50, 200)); + // Mock the reward pool transferring the payout to del_10 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 5)); + + // Given del_40 hasn't claimed and the reward pool has just earned 400 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 400)); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 421); + + // When + let (reward_pool, del_10, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_10).unwrap(); + + // Then + assert_eq!(payout, 40); + assert_eq!(del_10, del(10, 600)); + assert_eq!( + reward_pool, + rew( + 381, + // old pool points + points from new earnings - del points + // + // points from new earnings = new earnings(400) * primary_pool.points(100) + // del points = delegator.points(10) * new_earnings_since_last_claim(400) + (2_000 + 400 * 100) - 10 * 400, + 600 + ) + ); + // Mock the reward pool transferring the payout to del_10 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 40)); + + // Given del_40 + del_50 haven't claimed and the reward pool has earned 20 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 20)); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 401); + + // When + let (reward_pool, del_10, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_10).unwrap(); + + // Then + assert_eq!(payout, 2); // (200 del virtual points / 39,800 pool points) * 401 + assert_eq!(del_10, del(10, 620)); + assert_eq!(reward_pool, rew(399, (38_000 + 20 * 100) - 10 * 20, 620)); + // Mock the reward pool transferring the payout to del_10 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 2)); + + // When + let (reward_pool, del_40, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_40).unwrap(); + + // Then + assert_eq!(payout, 188); // (18,800 del virtual points / 39,800 pool points) * 399 + assert_eq!(del_40, del(40, 620)); + assert_eq!(reward_pool, rew(211, 39_800 - 40 * 470, 620)); + // Mock the reward pool transferring the payout to del_10 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 188)); + + // When + let (reward_pool, del_50, payout) = + Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_50).unwrap(); + + // Then + assert_eq!(payout, 211); // (21,000 / 21,000) * 211 + assert_eq!(del_50, del(50, 620)); + assert_eq!(reward_pool, rew(0, 21_000 - 50 * 420, 620)); + }); } } From 04afc2820c9b669c601827520e8aed023526b1d7 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 20 Jan 2022 12:27:14 -0800 Subject: [PATCH 017/299] Test: maybe_merge_pools_works --- frame/pools/src/lib.rs | 47 ++++++++++++++++----------- frame/pools/src/mock.rs | 8 ++--- frame/pools/src/tests.rs | 68 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 100 insertions(+), 23 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 8dd3cbe7413d7..403dfa0565a6e 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -30,7 +30,7 @@ use frame_support::{ pallet_prelude::*, storage::bounded_btree_map::BoundedBTreeMap, traits::{Currency, ExistenceRequirement, Get}, - DefaultNoBound, + DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_arithmetic::{FixedPointNumber, FixedU128}; @@ -65,7 +65,7 @@ type SubPoolsWithEra = BoundedBTreeMap, const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, frame_support::RuntimeDebugNoBound)] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] @@ -83,7 +83,7 @@ pub struct Delegator { unbonding_era: Option, } -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, frame_support::RuntimeDebugNoBound)] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] @@ -137,7 +137,7 @@ impl PrimaryPool { } } -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, frame_support::RuntimeDebugNoBound)] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] @@ -166,8 +166,8 @@ impl RewardPool { } } -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound)] -#[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound, RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] struct UnbondPool { @@ -175,6 +175,13 @@ struct UnbondPool { balance: BalanceOf, } +impl UnbondPool { + #[cfg(feature = "std")] + fn new(points: PointsOf, balance: BalanceOf) -> Self { + Self { points, balance } + } +} + impl UnbondPool { fn points_to_issue(&self, new_funds: BalanceOf) -> PointsOf { if self.balance.is_zero() || self.points.is_zero() { @@ -214,14 +221,14 @@ impl UnbondPool { } } -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound)] -#[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound, RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] struct SubPoolsContainer { /// A general, era agnostic pool of funds that have fully unbonded. The pools /// of `self.with_era` will lazily be merged into into this pool if they are - /// older then `current_era - T::MAX_UNBONDING`. + /// older then `current_era - MaxUnbonding`. no_era: UnbondPool, /// Map of era => unbond pools. with_era: SubPoolsWithEra, @@ -232,21 +239,25 @@ impl SubPoolsContainer { /// era. fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self { if current_era < T::MaxUnbonding::get().into() { - // For the first `T::MAX_UNBONDING` eras of the chain we don't need to do anything. - // I.E. if `MAX_UNBONDING` is 5 and we are in era 4 we can add a pool for this era and - // have exactly `MAX_UNBONDING` pools. + // For the first `0..MaxUnbonding` eras of the chain we don't need to do anything. + // I.E. if `MaxUnbonding` is 5 and we are in era 4 we can add a pool for this era and + // have exactly `MaxUnbonding` pools. return self; } - // I.E. if `MAX_UNBONDING` is 5 and current era is 10, we only want to retain pools 6..=10. - let oldest_era_to_keep = current_era - T::MaxUnbonding::get().saturating_add(1); + // I.E. if `MaxUnbonding` is 5 and current era is 10, we only want to retain pools 6..=10. + let newest_era_to_remove = current_era.saturating_sub(T::MaxUnbonding::get()); - let eras_to_remove: Vec<_> = - self.with_era.keys().cloned().filter(|era| *era < oldest_era_to_keep).collect(); + let eras_to_remove: Vec<_> = self + .with_era + .keys() + .cloned() + .filter(|era| *era <= newest_era_to_remove) + .collect(); for era in eras_to_remove { if let Some(p) = self.with_era.remove(&era) { - self.no_era.points.saturating_add(p.points); - self.no_era.balance.saturating_add(p.balance); + self.no_era.points = self.no_era.points.saturating_add(p.points); + self.no_era.balance = self.no_era.balance.saturating_add(p.balance); } else { // the world is broken } diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 2e57ea3e219a3..34c5221263eea 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -113,10 +113,6 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } -parameter_types! { - pub static MaxUnbonding: u32 = 5; -} - pub struct BalanceToU128; impl Convert for BalanceToU128 { fn convert(n: Balance) -> u128 { @@ -135,6 +131,10 @@ impl Convert for U128ToBalance { } } +parameter_types! { + pub static MaxUnbonding: u32 = 5; +} + impl pools::Config for Runtime { type Event = Event; type Currency = Balances; diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 38e1e4feb7a4a..5e71c4721199d 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -64,7 +64,73 @@ mod unbond_pool { // zero case } } -mod sub_pools_container {} +mod sub_pools_container { + use super::*; + + #[test] + fn maybe_merge_pools_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(::MaxUnbonding::get(), 5); + + // Given + let mut sp0 = SubPoolsContainer:: { + no_era: UnbondPool::::default(), + with_era: std::collections::BTreeMap::from([ + (0, UnbondPool::::new(10, 10)), + (1, UnbondPool::::new(10, 10)), + (2, UnbondPool::::new(20, 20)), + (3, UnbondPool::::new(30, 30)), + (4, UnbondPool::::new(40, 40)), + ]) + .try_into() + .unwrap(), + }; + + // When `current_era < MaxUnbonding`, + let sp1 = sp0.clone().maybe_merge_pools(3); + + // Then it exits early without modifications + assert_eq!(sp1, sp0); + + // When `current_era == MaxUnbonding`, + let mut sp1 = sp1.maybe_merge_pools(4); + + // Then it exits early without modifications + assert_eq!(sp1, sp0); + + // Given we have entries for era 0..=5 + sp1.with_era.insert(5, UnbondPool::::new(50, 50)); + sp0.with_era.insert(5, UnbondPool::::new(50, 50)); + + // When `current_era - MaxUnbonding == 0`, + let sp1 = sp1.maybe_merge_pools(5); + + // Then era 0 is merged into the `no_era` pool + sp0.no_era = sp0.with_era.remove(&0).unwrap(); + assert_eq!(sp1, sp0); + + // When `current_era - MaxUnbonding == 1` + let sp2 = sp1.maybe_merge_pools(6); + let era_1_pool = sp0.with_era.remove(&1).unwrap(); + + // Then era 1 is merged into the `no_era` pool + sp0.no_era.points += era_1_pool.points; + sp0.no_era.balance += era_1_pool.balance; + assert_eq!(sp2, sp0); + + // When `current_era - MaxUnbonding == 5`, so all pools with era <= 4 are removed + let sp3 = sp2.maybe_merge_pools(10); + + // Then all eras <= 5 are merged into the `no_era` pool + for era in 2..=5 { + let to_merge = sp0.with_era.remove(&era).unwrap(); + sp0.no_era.points += to_merge.points; + sp0.no_era.balance += to_merge.balance; + } + assert_eq!(sp3, sp0); + }); + } +} mod join {} From 077f5cb6afdddf140d24375b88fac5bb75c86613 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 20 Jan 2022 12:35:29 -0800 Subject: [PATCH 018/299] SubPoolsContainer => SubPools --- frame/pools/src/lib.rs | 43 ++++++++++++++++++++-------------------- frame/pools/src/tests.rs | 6 +++--- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 403dfa0565a6e..b0e83a2451b45 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -5,8 +5,8 @@ //! * primary pool: This pool represents the actively staked funds ... //! * rewards pool: The rewards earned by actively staked funds. Delegator can withdraw rewards once //! * sub pools: This a group of pools where we have a set of pools organized by era -//! (`SubPoolsContainer.with_era`) and one pool that is not associated with an era -//! (`SubsPoolsContainer.no_era`). Once a `with_era` pool is older then `current_era - +//! (`SubPools.with_era`) and one pool that is not associated with an era +//! (`SubPools.no_era`). Once a `with_era` pool is older then `current_era - //! MaxUnbonding`, its points and balance get merged into the `no_era` pool. //! //! # Joining @@ -60,7 +60,6 @@ macro_rules! log { type PoolId = u32; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -type PointsOf = BalanceOf; type SubPoolsWithEra = BoundedBTreeMap, ::MaxUnbonding>; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; @@ -73,7 +72,7 @@ pub struct Delegator { pool: PoolId, /// The quantity of points this delegator has in the primary pool or in a sub pool if /// `Self::unbonding_era` is some. - points: PointsOf, + points: BalanceOf, /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. /// Assuming no massive burning events, we expect this value to always be below total issuance. // TODO: ^ double check the above is an OK assumption @@ -88,14 +87,14 @@ pub struct Delegator { #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct PrimaryPool { - points: PointsOf, // Probably needs to be some type of BigUInt + points: BalanceOf, // Probably needs to be some type of BigUInt // The _Stash_ and _Controller_ account for the pool. account_id: T::AccountId, } impl PrimaryPool { /// Get the amount of points to issue for some new funds that will be bonded in the pool. - fn points_to_issue(&self, new_funds: BalanceOf) -> PointsOf { + fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); if bonded_balance.is_zero() || self.points.is_zero() { println!("bonded_balance={:?} points={:?}", bonded_balance, self.points); @@ -118,7 +117,7 @@ impl PrimaryPool { } // Get the amount of balance to unbond from the pool based on a delegator's points of the pool. - fn balance_to_unbond(&self, delegator_points: PointsOf) -> BalanceOf { + fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); if bonded_balance.is_zero() || delegator_points.is_zero() { // There is nothing to unbond @@ -171,19 +170,19 @@ impl RewardPool { #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] struct UnbondPool { - points: PointsOf, + points: BalanceOf, balance: BalanceOf, } impl UnbondPool { - #[cfg(feature = "std")] - fn new(points: PointsOf, balance: BalanceOf) -> Self { + #[cfg(test)] + fn new(points: BalanceOf, balance: BalanceOf) -> Self { Self { points, balance } } } impl UnbondPool { - fn points_to_issue(&self, new_funds: BalanceOf) -> PointsOf { + fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { if self.balance.is_zero() || self.points.is_zero() { // TODO this doesn't hold if the pool is totally slashed but we need some more logic for // that case @@ -203,7 +202,7 @@ impl UnbondPool { } } - fn balance_to_unbond(&self, delegator_points: PointsOf) -> BalanceOf { + fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { if self.balance.is_zero() || delegator_points.is_zero() { // There is nothing to unbond return Zero::zero(); @@ -225,7 +224,7 @@ impl UnbondPool { #[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] -struct SubPoolsContainer { +struct SubPools { /// A general, era agnostic pool of funds that have fully unbonded. The pools /// of `self.with_era` will lazily be merged into into this pool if they are /// older then `current_era - MaxUnbonding`. @@ -234,7 +233,7 @@ struct SubPoolsContainer { with_era: SubPoolsWithEra, } -impl SubPoolsContainer { +impl SubPools { /// Merge the oldest unbonding pool with an era into the general unbond pool with no associated /// era. fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self { @@ -332,8 +331,8 @@ pub mod pallet { /// Groups of unbonding pools. Each group of unbonding pools belongs to a primary pool, /// hence the name sub-pools. #[pallet::storage] - pub(crate) type SubPools = - CountedStorageMap<_, Twox64Concat, PoolId, SubPoolsContainer>; + pub(crate) type SubPoolsStorage = + CountedStorageMap<_, Twox64Concat, PoolId, SubPools>; #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] @@ -485,7 +484,7 @@ pub mod pallet { // Re-fetch the delegator because they where updated by `do_reward_payout`. let mut delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; // Note that we lazily create the unbonding pools here if they don't already exist - let sub_pools = SubPools::::get(delegator.pool).unwrap_or_default(); + let sub_pools = SubPoolsStorage::::get(delegator.pool).unwrap_or_default(); let current_era = T::StakingInterface::current_era(); let balance_to_unbond = primary_pool.balance_to_unbond(delegator.points); @@ -522,7 +521,7 @@ pub mod pallet { // Now that we know everything has worked write the items to storage. PrimaryPools::insert(delegator.pool, primary_pool); - SubPools::insert(delegator.pool, sub_pools); + SubPoolsStorage::insert(delegator.pool, sub_pools); Delegators::insert(who, delegator); Ok(()) @@ -539,7 +538,7 @@ pub mod pallet { return Err(Error::::NotUnbondedYet.into()); }; - let mut sub_pools = SubPools::::get(delegator.pool).unwrap_or_default(); + let mut sub_pools = SubPoolsStorage::::get(delegator.pool).unwrap_or_default(); let balance_to_unbond = if let Some(pool) = sub_pools.with_era.get_mut(¤t_era) { let balance_to_unbond = pool.balance_to_unbond(delegator.points); @@ -567,7 +566,7 @@ pub mod pallet { ExistenceRequirement::AllowDeath, )?; - SubPools::::insert(delegator.pool, sub_pools); + SubPoolsStorage::::insert(delegator.pool, sub_pools); Self::deposit_event(Event::::Withdrawn { delegator: who, @@ -779,7 +778,7 @@ impl Pallet { apply_era: EraIndex, ) -> Option<(BalanceOf, BTreeMap>)> { let pool_id = PoolIds::::get(pool_account)?; - let mut sub_pools = SubPools::::get(pool_id).unwrap_or_default(); + let mut sub_pools = SubPoolsStorage::::get(pool_id).unwrap_or_default(); // TODO double check why we do slash_era + 1 let affected_range = (slash_era + 1)..=apply_era; @@ -832,7 +831,7 @@ impl Pallet { }) .collect(); - SubPools::::insert(pool_id, sub_pools); + SubPoolsStorage::::insert(pool_id, sub_pools); let slashed_bonded_pool_balance = { let pre_slash_balance = T::BalanceToU128::convert(bonded_balance); diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 5e71c4721199d..ffe731db4d6f0 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -9,7 +9,7 @@ fn test_setup_works() { ExtBuilder::default().build_and_execute(|| { assert_eq!(PrimaryPools::::count(), 1); assert_eq!(RewardPools::::count(), 1); - assert_eq!(SubPools::::count(), 0); + assert_eq!(SubPoolsStorage::::count(), 0); assert_eq!(Delegators::::count(), 1); assert_eq!( @@ -64,7 +64,7 @@ mod unbond_pool { // zero case } } -mod sub_pools_container { +mod sub_pools { use super::*; #[test] @@ -73,7 +73,7 @@ mod sub_pools_container { assert_eq!(::MaxUnbonding::get(), 5); // Given - let mut sp0 = SubPoolsContainer:: { + let mut sp0 = SubPools:: { no_era: UnbondPool::::default(), with_era: std::collections::BTreeMap::from([ (0, UnbondPool::::new(10, 10)), From b7521023f52eed2eb239839d1acf62306fbea4e1 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 20 Jan 2022 13:40:26 -0800 Subject: [PATCH 019/299] Factor out points_to_issue; cover with tests --- frame/pools/src/lib.rs | 101 ++++++++++++++++----------------------- frame/pools/src/tests.rs | 22 ++++++++- 2 files changed, 60 insertions(+), 63 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index b0e83a2451b45..814dfb8e9b496 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -5,9 +5,9 @@ //! * primary pool: This pool represents the actively staked funds ... //! * rewards pool: The rewards earned by actively staked funds. Delegator can withdraw rewards once //! * sub pools: This a group of pools where we have a set of pools organized by era -//! (`SubPools.with_era`) and one pool that is not associated with an era -//! (`SubPools.no_era`). Once a `with_era` pool is older then `current_era - -//! MaxUnbonding`, its points and balance get merged into the `no_era` pool. +//! (`SubPools.with_era`) and one pool that is not associated with an era (`SubPools.no_era`). +//! Once a `with_era` pool is older then `current_era - MaxUnbonding`, its points and balance get +//! merged into the `no_era` pool. //! //! # Joining //! @@ -64,6 +64,36 @@ type SubPoolsWithEra = BoundedBTreeMap, const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; +/// Calculate the number of points to issue: `(current_points / current_balance) * new_funds`. +fn points_to_issue( + current_balance: BalanceOf, + current_points: BalanceOf, + new_funds: BalanceOf, +) -> BalanceOf { + match (current_balance.is_zero(), current_points.is_zero()) { + (true, true) | (false, true) => { + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) + }, + (true, false) => { + // The pool was totally slashed. + + // This is the equivalent of `(current_points / 1) * new_funds`. This is more favorable + // to the joiner then simply returning `new_funds`. + new_funds.saturating_mul(current_points) + }, + (false, false) => { + // REMINDER: `saturating_from_rational` panics if denominator is zero + let points_per_balance = FixedU128::saturating_from_rational( + T::BalanceToU128::convert(current_points), + T::BalanceToU128::convert(current_balance), + ); + let new_funds = T::BalanceToU128::convert(new_funds); + + T::U128ToBalance::convert(points_per_balance.saturating_mul_int(new_funds)) + }, + } +} + #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] @@ -96,24 +126,7 @@ impl PrimaryPool { /// Get the amount of points to issue for some new funds that will be bonded in the pool. fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); - if bonded_balance.is_zero() || self.points.is_zero() { - println!("bonded_balance={:?} points={:?}", bonded_balance, self.points); - // TODO this doesn't hold if the pool is totally slashed but we need some more logic for - // that case - debug_assert!(bonded_balance.is_zero() && self.points.is_zero()); - - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) - } else { - let points_per_balance = { - let balance = T::BalanceToU128::convert(bonded_balance); - let points = T::BalanceToU128::convert(self.points); - // REMINDER: `saturating_from_rational` panics if denominator is zero - FixedU128::saturating_from_rational(points, balance) - }; - let new_funds = T::BalanceToU128::convert(new_funds); - - T::U128ToBalance::convert(points_per_balance.saturating_mul_int(new_funds)) - } + points_to_issue::(bonded_balance, self.points, new_funds) } // Get the amount of balance to unbond from the pool based on a delegator's points of the pool. @@ -183,23 +196,7 @@ impl UnbondPool { impl UnbondPool { fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { - if self.balance.is_zero() || self.points.is_zero() { - // TODO this doesn't hold if the pool is totally slashed but we need some more logic for - // that case - debug_assert!(self.balance.is_zero() && self.points.is_zero()); - - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) - } else { - let points_per_balance = { - let balance = T::BalanceToU128::convert(self.balance); - let points = T::BalanceToU128::convert(self.points); - // REMINDER: `saturating_from_rational` panics if denominator is zero - FixedU128::saturating_from_rational(points, balance) - }; - let new_funds = T::BalanceToU128::convert(new_funds); - - T::U128ToBalance::convert(points_per_balance.saturating_mul_int(new_funds)) - } + points_to_issue::(self.balance, self.points, new_funds) } fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { @@ -856,30 +853,12 @@ impl PoolsInterface for Pallet { } } -// -// - slashing +// TODO // - tests // - force pool creation // - rebond_rewards -// - force pool update +// - pool update targets +// - pool block - don't allow new joiners (or bond_extra) +// - pool begin destroy - unbond the entire pool balance +// - pool end destroy - once all subpools are empty delete everything from storage // - force pool delete? - -// impl Pallet { -// do_create_pool( -// creator: T::AccountId, -// targets: Vec, -// amount: BalanceOf -// ) -> DispatchResult { -// Create Stash/Controller account based on parent block hash, block number, and extrinsic index -// Create Reward Pool account based on Stash/Controller account -// Move `amount` to the stash / controller account -// Read in `bondable` - the free balance that we can bond after any neccesary reserv etc - -// Bond with `amount`, ensuring that it is over the minimum bond (by min) -// (might has need to ensure number of targets etc is valid) - -// Generate a pool id (look at how assets IDs are generated for inspiration) - -// -// } -// } diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index ffe731db4d6f0..38956ddd1ff41 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -37,11 +37,29 @@ fn test_setup_works() { }) } -mod primary_pool { +mod points_to_issue { + use super::*; #[test] fn points_to_issue_works() { - // zero case + ExtBuilder::default().build_and_execute(|| { + let points_to_issue = points_to_issue::; + // 1 points : 1 balance ratio returns `new_funds` + assert_eq!(points_to_issue(100, 100, 10), 10); + // 2 points : 1 balance ratio returns double of `new_funds` + assert_eq!(points_to_issue(50, 100, 10), 20); + // 1 points: 2 balance ratio returns half of `new_funds + assert_eq!(points_to_issue(100, 50, 10), 5); + // 100 points : 0 balance ratio returns `new_funds * 100` + assert_eq!(points_to_issue(0, 100, 10), 100 * 10); + // 0 points : 100 balance ratio returns `new_funds` + assert_eq!(points_to_issue(100, 0, 10), 10); + }); } +} + +mod primary_pool { + #[test] + fn points_to_issue_works() {} #[test] fn balance_to_unbond_works() { From dba684b525f44f2706ebf11d7a6510379c668636 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 20 Jan 2022 14:39:52 -0800 Subject: [PATCH 020/299] Factor out balance_to_unbond and test it --- frame/pools/src/lib.rs | 53 ++++++++++++++++++---------------------- frame/pools/src/tests.rs | 40 ++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 34 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 814dfb8e9b496..5a09674e0d277 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -64,7 +64,7 @@ type SubPoolsWithEra = BoundedBTreeMap, const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; -/// Calculate the number of points to issue: `(current_points / current_balance) * new_funds`. +/// Calculate the number of points to issue from a pool as `(current_points / current_balance) * new_funds` except for some 0 edge cases; see logic and tests for details. fn points_to_issue( current_balance: BalanceOf, current_points: BalanceOf, @@ -94,6 +94,27 @@ fn points_to_issue( } } +// Calculate the balance of a pool to unbond as `(current_balance / current_points) * delegator_points`. Returns zero if any of the inputs are zero. +fn balance_to_unbond( + current_balance: BalanceOf, + current_points: BalanceOf, + delegator_points: BalanceOf, +) -> BalanceOf { + if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { + // There is nothing to unbond + return Zero::zero(); + } + + // REMINDER: `saturating_from_rational` panics if denominator is zero + let balance_per_share = FixedU128::saturating_from_rational( + T::BalanceToU128::convert(current_balance), + T::BalanceToU128::convert(current_points), + ); + let delegator_points = T::BalanceToU128::convert(delegator_points); + + T::U128ToBalance::convert(balance_per_share.saturating_mul_int(delegator_points)) +} + #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] @@ -132,20 +153,7 @@ impl PrimaryPool { // Get the amount of balance to unbond from the pool based on a delegator's points of the pool. fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); - if bonded_balance.is_zero() || delegator_points.is_zero() { - // There is nothing to unbond - return Zero::zero(); - } - - let balance_per_share = { - let balance = T::BalanceToU128::convert(bonded_balance); - let points = T::BalanceToU128::convert(self.points); - // REMINDER: `saturating_from_rational` panics if denominator is zero - FixedU128::saturating_from_rational(balance, points) - }; - let delegator_points = T::BalanceToU128::convert(delegator_points); - - T::U128ToBalance::convert(balance_per_share.saturating_mul_int(delegator_points)) + balance_to_unbond::(bonded_balance, self.points, delegator_points) } } @@ -200,20 +208,7 @@ impl UnbondPool { } fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { - if self.balance.is_zero() || delegator_points.is_zero() { - // There is nothing to unbond - return Zero::zero(); - } - - let balance_per_share = { - let balance = T::BalanceToU128::convert(self.balance); - let points = T::BalanceToU128::convert(self.points); - // REMINDER: `saturating_from_rational` panics if denominator is zero - FixedU128::saturating_from_rational(balance, points) - }; - let delegator_points = T::BalanceToU128::convert(delegator_points); - - T::U128ToBalance::convert(balance_per_share.saturating_mul_int(delegator_points)) + balance_to_unbond::(self.balance, self.points, delegator_points) } } diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 38956ddd1ff41..97108e1e76414 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -43,16 +43,46 @@ mod points_to_issue { fn points_to_issue_works() { ExtBuilder::default().build_and_execute(|| { let points_to_issue = points_to_issue::; - // 1 points : 1 balance ratio returns `new_funds` + // 1 points : 1 balance ratio assert_eq!(points_to_issue(100, 100, 10), 10); - // 2 points : 1 balance ratio returns double of `new_funds` + assert_eq!(points_to_issue(100, 100, 0), 0); + // 2 points : 1 balance ratio assert_eq!(points_to_issue(50, 100, 10), 20); - // 1 points: 2 balance ratio returns half of `new_funds + // 1 points: 2 balance ratio assert_eq!(points_to_issue(100, 50, 10), 5); - // 100 points : 0 balance ratio returns `new_funds * 100` + // 100 points : 0 balance ratio assert_eq!(points_to_issue(0, 100, 10), 100 * 10); - // 0 points : 100 balance ratio returns `new_funds` + // 0 points : 100 balance ratio assert_eq!(points_to_issue(100, 0, 10), 10); + // 10 points : 3 balance ratio + assert_eq!(points_to_issue(30, 100, 10), 33); + // 2 points : 3 balance ratio + assert_eq!(points_to_issue(300, 200, 10), 6); + }); + } +} + +mod balance_to_unbond { + use super::*; + #[test] + fn balance_to_unbond_works() { + ExtBuilder::default().build_and_execute(|| { + let balance_to_unbond = balance_to_unbond::; + // 1 balance : 1 points ratio + assert_eq!(balance_to_unbond(100, 100, 10), 10); + assert_eq!(balance_to_unbond(100, 100, 0), 0); + // 1 balance : 2 points ratio + assert_eq!(balance_to_unbond(50, 100, 10), 5); + // 2 balance : 1 points ratio + assert_eq!(balance_to_unbond(100, 50, 10), 20); + // 100 balance : 0 points ratio + assert_eq!(balance_to_unbond(100, 0, 10), 0); + // 0 balance : 100 points ratio + assert_eq!(balance_to_unbond(0, 100, 10), 0); + // 10 balance : 3 points ratio + assert_eq!(balance_to_unbond(100, 30, 10), 33); + // 2 balance : 3 points ratio + assert_eq!(balance_to_unbond(200, 300, 10), 6); }); } } From 38648803e9855c65ae97ac5513b7a2954dd0798f Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 20 Jan 2022 19:03:27 -0800 Subject: [PATCH 021/299] Test: join --- frame/pools/src/lib.rs | 135 ++++++++++++++++++++++++---------- frame/pools/src/mock.rs | 11 ++- frame/pools/src/tests.rs | 120 +++++++++++++++++++++++++++++- primitives/staking/src/lib.rs | 4 + 4 files changed, 224 insertions(+), 46 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 5a09674e0d277..9d73f59523ee2 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -22,6 +22,10 @@ //! # Negatives //! - no voting //! - .. +//! +//! RUNTIME BUILDER WARNINGS +//! - watch out for overflow of `RewardPoints` and `BalanceOf` types. Consider the chains total +//! issuance, staking reward rate, and burn rate. #![cfg_attr(not(feature = "std"), no_std)] @@ -34,9 +38,11 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_arithmetic::{FixedPointNumber, FixedU128}; -use sp_runtime::traits::{Convert, One, Saturating, StaticLookup, TrailingZeroInput, Zero}; +use sp_runtime::traits::{ + Bounded, Convert, One, Saturating, StaticLookup, TrailingZeroInput, Zero, +}; use sp_staking::{EraIndex, PoolsInterface, StakingInterface}; -use sp_std::collections::btree_map::BTreeMap; +use sp_std::{collections::btree_map::BTreeMap, ops::Div}; #[cfg(test)] mod mock; @@ -61,19 +67,21 @@ type PoolId = u32; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type SubPoolsWithEra = BoundedBTreeMap, ::MaxUnbonding>; +// NOTE: this assumes the balance type u128 or smaller. +// type RewardPoints = u256; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; -/// Calculate the number of points to issue from a pool as `(current_points / current_balance) * new_funds` except for some 0 edge cases; see logic and tests for details. +/// Calculate the number of points to issue from a pool as `(current_points / current_balance) * +/// new_funds` except for some zero edge cases; see logic and tests for details. fn points_to_issue( current_balance: BalanceOf, current_points: BalanceOf, new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => { - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) - }, + (true, true) | (false, true) => + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { // The pool was totally slashed. @@ -94,7 +102,8 @@ fn points_to_issue( } } -// Calculate the balance of a pool to unbond as `(current_balance / current_points) * delegator_points`. Returns zero if any of the inputs are zero. +// Calculate the balance of a pool to unbond as `(current_balance / current_points) * +// delegator_points`. Returns zero if any of the inputs are zero. fn balance_to_unbond( current_balance: BalanceOf, current_points: BalanceOf, @@ -102,7 +111,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero(); + return Zero::zero() } // REMINDER: `saturating_from_rational` panics if denominator is zero @@ -138,7 +147,7 @@ pub struct Delegator { #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct PrimaryPool { - points: BalanceOf, // Probably needs to be some type of BigUInt + points: BalanceOf, // The _Stash_ and _Controller_ account for the pool. account_id: T::AccountId, } @@ -155,6 +164,30 @@ impl PrimaryPool { let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); balance_to_unbond::(bonded_balance, self.points, delegator_points) } + + // Check that the pool can accept a member with `new_funds`. + fn ok_to_join_with(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { + let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); + let points_to_balance_ratio = FixedU128::saturating_from_rational( + T::BalanceToU128::convert(self.points), + T::BalanceToU128::convert(bonded_balance), + ); + // TODO make sure these checks make sense. Taken from staking design chat with Al + // Pool points can inflate relative to balance, but only if the pool is slashed. + // + // If we cap the ratio of points:balance so one cannot join a pool that has been slashed + // 90%, + ensure!(points_to_balance_ratio < 10.into(), Error::::OverflowRisk); + // while restricting the balance to 1/10th of max total issuance, + ensure!( + new_funds.saturating_add(bonded_balance) < + BalanceOf::::max_value().div(10u32.into()), + Error::::OverflowRisk + ); + // then we can be decently confident the bonding pool points will not overflow + // `BalanceOf`. + Ok(()) + } } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] @@ -162,15 +195,20 @@ impl PrimaryPool { #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct RewardPool { + /// The reward destination for the pool. + account_id: T::AccountId, /// The balance of this reward pool after the last claimed payout. balance: BalanceOf, - /// The points of this reward pool after the last claimed payout - points: BalanceOf, /// The total earnings _ever_ of this reward pool after the last claimed payout. I.E. the sum /// of all incoming balance through the pools life. + /// + /// NOTE: We assume this will always be less than total issuance and thus can use the runtimes + /// `Balance` type. However in a chain with a burn rate higher than the rate this increases, + /// this type should be bigger than `Balance`. total_earnings: BalanceOf, - /// The reward destination for the pool. - account_id: T::AccountId, + /// The total points of this reward pool after the last claimed payout. + // points: RewardPoints, + points: BalanceOf, } impl RewardPool { @@ -233,7 +271,7 @@ impl SubPools { // For the first `0..MaxUnbonding` eras of the chain we don't need to do anything. // I.E. if `MaxUnbonding` is 5 and we are in era 4 we can add a pool for this era and // have exactly `MaxUnbonding` pools. - return self; + return self } // I.E. if `MaxUnbonding` is 5 and current era is 10, we only want to retain pools 6..=10. @@ -285,6 +323,12 @@ pub mod pallet { // Infallible method for converting `u128` to `Currency::Balance`. type U128ToBalance: Convert>; + // Infallible method for converting `Currency::Balance` to `RewardPoints`. + // type BalanceToReward: Convert, RewardPoints>; + + // Infallible method for converting `RewardPoints` to `Currency::Balance`. + // type RewardToBalance: Convert>; + /// The interface for nominating. type StakingInterface: StakingInterface< Balance = BalanceOf, @@ -359,6 +403,10 @@ pub mod pallet { IdInUse, /// The amount does not meet the minimum bond to start nominating. MinimiumBondNotMet, + /// The transaction could not be executed due to overflow risk for the pool. + OverflowRisk, + /// An error from the staking pallet. + StakingError, } #[pallet::call] @@ -372,25 +420,28 @@ pub mod pallet { #[pallet::weight(666)] pub fn join(origin: OriginFor, amount: BalanceOf, target: PoolId) -> DispatchResult { let who = ensure_signed(origin)?; - // if a delegator already exists that means they already belong to a pool + // If a delegator already exists that means they already belong to a pool ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); - // Ensure that the `target` pool exists, let mut primary_pool = PrimaryPools::::get(target).ok_or(Error::::PoolNotFound)?; - // And that `amount` will meet the minimum bond - let old_free_balance = T::Currency::free_balance(&primary_pool.account_id); - ensure!( - old_free_balance.saturating_add(amount) >= T::StakingInterface::minimum_bond(), - Error::::InsufficientBond - ); - // Note that we don't actually care about writing the reward pool, we just need its + primary_pool.ok_to_join_with(amount)?; + + // The pool should always be created in such a way its in a state to bond extra, but if + // the active balance is slashed below the minimum bonded or the account cannot be + // found, we exit early. + if !T::StakingInterface::can_bond_extra(&primary_pool.account_id, amount) { + return Err(Error::::StakingError.into()) + } + + // We don't actually care about writing the reward pool, we just need its // total earnings at this point in time. let reward_pool = RewardPools::::get(target) .ok_or(Error::::RewardPoolNotFound)? // This is important because we want the most up-to-date total earnings. .update_total_earnings_and_balance(); + let old_free_balance = T::Currency::free_balance(&primary_pool.account_id); // Transfer the funds to be bonded from `who` to the pools account so the pool can then // go bond them. T::Currency::transfer( @@ -399,32 +450,36 @@ pub mod pallet { amount, ExistenceRequirement::KeepAlive, )?; + + // Try not to bail after the above transfer + // This should now include the transferred balance. let new_free_balance = T::Currency::free_balance(&primary_pool.account_id); // Get the exact amount we can bond extra. let exact_amount_to_bond = new_free_balance.saturating_sub(old_free_balance); - // We must calculate the points to issue *before* we bond `who`'s funds, else the // points:balance ratio will be wrong. let new_points = primary_pool.points_to_issue(exact_amount_to_bond); primary_pool.points = primary_pool.points.saturating_add(new_points); - let delegator = Delegator:: { - pool: target, - points: new_points, - // TODO double check that this is ok. - // At best the reward pool has the rewards up through the previous era. If the - // delegator joins prior to the snapshot they will benefit from the rewards of the - // current era despite not contributing to the pool's vote weight. If they join - // after the snapshot is taken they will benefit from the rewards of the next *2* - // eras because their vote weight will not be counted until the snapshot in current - // era + 1. - reward_pool_total_earnings: reward_pool.total_earnings, - unbonding_era: None, - }; T::StakingInterface::bond_extra(&primary_pool.account_id, exact_amount_to_bond)?; - Delegators::insert(who.clone(), delegator); + Delegators::insert( + who.clone(), + Delegator:: { + pool: target, + points: new_points, + // TODO double check that this is ok. + // At best the reward pool has the rewards up through the previous era. If the + // delegator joins prior to the snapshot they will benefit from the rewards of + // the current era despite not contributing to the pool's vote weight. If they + // join after the snapshot is taken they will benefit from the rewards of the + // next *2* eras because their vote weight will not be counted until the + // snapshot in current era + 1. + reward_pool_total_earnings: reward_pool.total_earnings, + unbonding_era: None, + }, + ); PrimaryPools::insert(target, primary_pool); Self::deposit_event(Event::::Joined { @@ -527,7 +582,7 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); if current_era.saturating_sub(unbonding_era) < T::StakingInterface::bonding_duration() { - return Err(Error::::NotUnbondedYet.into()); + return Err(Error::::NotUnbondedYet.into()) }; let mut sub_pools = SubPoolsStorage::::get(delegator.pool).unwrap_or_default(); @@ -796,7 +851,7 @@ impl Pallet { // Panics if denominator is zero let slash_ratio = if total_affected_balance <= Zero::zero() { - return Some((Zero::zero(), Default::default())); + return Some((Zero::zero(), Default::default())) } else { // REMINDER: `saturating_from_rational` panics if denominator is zero FixedU128::saturating_from_rational( diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 34c5221263eea..1406d41435dd0 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -13,12 +13,13 @@ pub const REWARDS_ACCOUNT: u32 = 736857005; parameter_types! { static CurrentEra: EraIndex = 0; - pub static BondedBalanceMap: std::collections::HashMap = Default::default(); + static BondedBalanceMap: std::collections::HashMap = Default::default(); + pub static CanBondExtra: bool = true; } pub struct StakingMock; impl StakingMock { - fn set_bonded_balance(who: AccountId, bonded: Balance) { + pub(crate) fn set_bonded_balance(who: AccountId, bonded: Balance) { BONDED_BALANCE_MAP.with(|m| m.borrow_mut().insert(who.clone(), bonded)); } } @@ -44,6 +45,10 @@ impl sp_staking::StakingInterface for StakingMock { BondedBalanceMap::get().get(who).map(|v| *v).unwrap_or_default() } + fn can_bond_extra(_: &Self::AccountId, _: Self::Balance) -> bool { + CanBondExtra::get() + } + fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { // Simulate bond extra in `join` BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(who).unwrap() += extra); @@ -98,7 +103,7 @@ impl frame_system::Config for Runtime { } parameter_types! { - pub static ExistentialDeposit: Balance = 1; + pub static ExistentialDeposit: Balance = 5; } impl pallet_balances::Config for Runtime { diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 97108e1e76414..4dde812d9851e 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1,7 +1,10 @@ //! Generally for pool ids we use 0-9 and delegator ids 10-99. use super::*; -use crate::mock::{Balances, ExtBuilder, Pools, Runtime, PRIMARY_ACCOUNT, REWARDS_ACCOUNT}; +use crate::mock::{ + Balance, Balances, CanBondExtra, ExtBuilder, Origin, Pools, Runtime, StakingMock, System, + PRIMARY_ACCOUNT, REWARDS_ACCOUNT, +}; use frame_support::{assert_noop, assert_ok}; #[test] @@ -88,6 +91,7 @@ mod balance_to_unbond { } mod primary_pool { + use super::*; #[test] fn points_to_issue_works() {} @@ -95,12 +99,18 @@ mod primary_pool { fn balance_to_unbond_works() { // zero case } + + #[test] + fn ok_to_join_with_works() { + ExtBuilder::default().build_and_execute(|| {}); + } } + mod reward_pool { - use super::*; #[test] fn update_total_earnings_and_balance_works() {} } + mod unbond_pool { #[test] fn points_to_issue_works() { @@ -180,7 +190,111 @@ mod sub_pools { } } -mod join {} +mod join { + use super::*; + + #[test] + fn join_works() { + ExtBuilder::default().build_and_execute(|| { + Balances::make_free_balance_be(&11, 5 + 2); + + assert!(!Delegators::::contains_key(&11)); + + assert_ok!(Pools::join(Origin::signed(11), 2, 0)); + + // Storage is updated correctly + assert_eq!( + Delegators::::get(&11).unwrap(), + Delegator:: { + pool: 0, + points: 2, + reward_pool_total_earnings: 0, + unbonding_era: None + } + ); + assert_eq!( + PrimaryPools::::get(&0).unwrap(), + PrimaryPool:: { points: 12, account_id: PRIMARY_ACCOUNT } + ); + }); + } + + fn join_works_with_a_slashed_pool() { + ExtBuilder::default().build_and_execute(|| { + Balances::make_free_balance_be(&11, 5 + 2); + + assert!(!Delegators::::contains_key(&11)); + + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 0); + + assert_ok!(Pools::join(Origin::signed(11), 2, 0)); + + // Storage is updated correctly + assert_eq!( + Delegators::::get(&11).unwrap(), + Delegator:: { + pool: 0, + points: 20, + reward_pool_total_earnings: 0, + unbonding_era: None + } + ); + assert_eq!( + PrimaryPools::::get(&0).unwrap(), + PrimaryPool:: { points: 30, account_id: PRIMARY_ACCOUNT } + ); + }); + } + + #[test] + fn join_errors_correctly() { + use super::*; + ExtBuilder::default().build_and_execute(|| { + assert_noop!( + Pools::join(Origin::signed(10), 420, 420), + Error::::AccountBelongsToOtherPool + ); + + assert_noop!(Pools::join(Origin::signed(11), 420, 420), Error::::PoolNotFound); + + PrimaryPools::::insert( + 1, + PrimaryPool:: { points: 100, account_id: 123 }, + ); + // Force the points:balance ratio to 100/10 (so 10) + StakingMock::set_bonded_balance(123, 10); + assert_noop!(Pools::join(Origin::signed(11), 420, 1), Error::::OverflowRisk); + + // Force the points:balance ratio to be a valid 100/100 + StakingMock::set_bonded_balance(123, 100); + // Cumulative balance is > 1/10 of Balance::MAX + assert_noop!( + Pools::join(Origin::signed(11), Balance::MAX / 10 - 100, 1), + Error::::OverflowRisk + ); + + CanBondExtra::set(false); + assert_noop!(Pools::join(Origin::signed(11), 420, 1), Error::::StakingError); + CanBondExtra::set(true); + + assert_noop!( + Pools::join(Origin::signed(11), 420, 1), + Error::::RewardPoolNotFound + ); + RewardPools::::insert( + 1, + RewardPool:: { + balance: Zero::zero(), + points: Zero::zero(), + total_earnings: Zero::zero(), + account_id: 321, + }, + ); + + // Skipping Currency::transfer & StakingInterface::bond_extra errors + }); + } +} mod claim_payout { use super::*; diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 9ce1f7dcb1593..e5fcba513b29c 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -66,6 +66,10 @@ pub trait StakingInterface { /// Balance `controller` has bonded for nominating. fn bonded_balance(controller: &Self::AccountId) -> Self::Balance; + /// If the given staker can successfully call `bond_extra` with `extra`. Assumes the `extra` + /// balance will be transferred in the stash. + fn can_bond_extra(controller: &Self::AccountId, extra: Self::Balance) -> bool; + fn bond_extra(controller: &Self::AccountId, extra: Self::Balance) -> DispatchResult; fn unbond(controller: &Self::AccountId, value: Self::Balance) -> DispatchResult; From 07fc9434bfa8bd5d7b8a04e8e0637179c124097b Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 20 Jan 2022 19:37:09 -0800 Subject: [PATCH 022/299] Test: ok_to_join_with --- frame/pools/src/lib.rs | 2 ++ frame/pools/src/tests.rs | 59 ++++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 9d73f59523ee2..a60e18cd7556e 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -168,6 +168,8 @@ impl PrimaryPool { // Check that the pool can accept a member with `new_funds`. fn ok_to_join_with(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); + ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); + let points_to_balance_ratio = FixedU128::saturating_from_rational( T::BalanceToU128::convert(self.points), T::BalanceToU128::convert(bonded_balance), diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 4dde812d9851e..5fd251c7ef8ba 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1,5 +1,3 @@ -//! Generally for pool ids we use 0-9 and delegator ids 10-99. - use super::*; use crate::mock::{ Balance, Balances, CanBondExtra, ExtBuilder, Origin, Pools, Runtime, StakingMock, System, @@ -102,7 +100,31 @@ mod primary_pool { #[test] fn ok_to_join_with_works() { - ExtBuilder::default().build_and_execute(|| {}); + ExtBuilder::default().build_and_execute(|| { + let pool = PrimaryPool:: { points: 100, account_id: 123 }; + + // Simulate a 100% slashed pool + StakingMock::set_bonded_balance(123, 0); + assert_noop!(pool.ok_to_join_with(100), Error::::OverflowRisk); + + // Simulate a 89% + StakingMock::set_bonded_balance(123, 11); + assert_ok!(pool.ok_to_join_with(100)); + + // Simulate a 90% slashed pool + StakingMock::set_bonded_balance(123, 10); + assert_noop!(pool.ok_to_join_with(100), Error::::OverflowRisk); + + let bonded = 100; + StakingMock::set_bonded_balance(123, bonded); + // New bonded balance would be over 1/10th of Balance type + assert_noop!( + pool.ok_to_join_with(Balance::MAX / 10 - bonded), + Error::::OverflowRisk + ); + // and a sanity check + assert_ok!(pool.ok_to_join_with(Balance::MAX / 100 - bonded + 1),); + }); } } @@ -219,33 +241,6 @@ mod join { }); } - fn join_works_with_a_slashed_pool() { - ExtBuilder::default().build_and_execute(|| { - Balances::make_free_balance_be(&11, 5 + 2); - - assert!(!Delegators::::contains_key(&11)); - - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 0); - - assert_ok!(Pools::join(Origin::signed(11), 2, 0)); - - // Storage is updated correctly - assert_eq!( - Delegators::::get(&11).unwrap(), - Delegator:: { - pool: 0, - points: 20, - reward_pool_total_earnings: 0, - unbonding_era: None - } - ); - assert_eq!( - PrimaryPools::::get(&0).unwrap(), - PrimaryPool:: { points: 30, account_id: PRIMARY_ACCOUNT } - ); - }); - } - #[test] fn join_errors_correctly() { use super::*; @@ -257,6 +252,10 @@ mod join { assert_noop!(Pools::join(Origin::signed(11), 420, 420), Error::::PoolNotFound); + // Force the pools bonded balance to 0, simulating a 100% slash + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 0); + assert_noop!(Pools::join(Origin::signed(11), 420, 0), Error::::OverflowRisk); + PrimaryPools::::insert( 1, PrimaryPool:: { points: 100, account_id: 123 }, From 99c3973d6a737b25a64cac61ea8f454b472c4dfd Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 21 Jan 2022 14:53:00 -0800 Subject: [PATCH 023/299] Do not use FixedU128 in calcualte_reward_payouts --- frame/pools/src/lib.rs | 24 ++++---- frame/pools/src/tests.rs | 117 +++++++++++++++++++++++---------------- frame/staking/src/lib.rs | 2 + 3 files changed, 85 insertions(+), 58 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index a60e18cd7556e..1031d4b19e9f5 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -576,6 +576,9 @@ pub mod pallet { Ok(()) } + // TODO the first person that withdraws for a pool withdraws there funds and then remaining + // funds and points are merged into the `no_era` pool - then for unbond pools we can just + // read balance from the unlocking chunks in staking #[pallet::weight(666)] pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -598,6 +601,7 @@ pub mod pallet { } else { // A pool does not belong to this era, so it must have been merged to the era-less // pool. + // trailing_pool???? let balance_to_unbond = sub_pools.no_era.balance_to_unbond(delegator.points); sub_pools.no_era.points = sub_pools.no_era.points.saturating_sub(delegator.points); sub_pools.no_era.balance = @@ -755,15 +759,14 @@ impl Pallet { let delegator_payout = if delegator_virtual_points.is_zero() || current_points.is_zero() { BalanceOf::::zero() } else { - // REMINDER: `saturating_from_rational` panics if denominator is zero - let delegator_ratio_of_points = FixedU128::saturating_from_rational( - T::BalanceToU128::convert(delegator_virtual_points), - T::BalanceToU128::convert(current_points), - ); - - let payout = delegator_ratio_of_points - .saturating_mul_int(T::BalanceToU128::convert(reward_pool.balance)); - T::U128ToBalance::convert(payout) + // `(delegator_virtual_points * reward_pool.balance) / current_points` is equivalent to + // `(delegator_virtual_points / current_points) * reward_pool.balance` + let payout = delegator_virtual_points + .saturating_mul(reward_pool.balance) + // We check for zero above + .div(current_points); + + payout }; // Record updates @@ -832,6 +835,7 @@ impl Pallet { // TODO double check why we do slash_era + 1 let affected_range = (slash_era + 1)..=apply_era; + // TODO Can have this as an input because we are inside of staking ledger let bonded_balance = T::StakingInterface::bonded_balance(pool_account); // Note that this doesn't count the balance in the `no_era` pool @@ -845,7 +849,7 @@ impl Pallet { }); let total_affected_balance = bonded_balance.saturating_add(unbonding_affected_balance); - if slash_amount < total_affected_balance { + if slash_amount > total_affected_balance { // TODO this shouldn't happen as long as MaxBonding pools is greater thant the slash // defer duration, which it should implicitly be because we expect it be longer then the // UnbondindDuration. TODO clearly document these assumptions diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 5fd251c7ef8ba..7f0459a814286 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1,6 +1,6 @@ use super::*; use crate::mock::{ - Balance, Balances, CanBondExtra, ExtBuilder, Origin, Pools, Runtime, StakingMock, System, + Balance, Balances, CanBondExtra, ExtBuilder, Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, }; use frame_support::{assert_noop, assert_ok}; @@ -129,8 +129,30 @@ mod primary_pool { } mod reward_pool { - #[test] - fn update_total_earnings_and_balance_works() {} + // use super::*; + + // #[test] + // fn update_total_earnings_and_balance_works() { + // ExtBuilder::default().build_and_execute(|| { + // let pool = RewardPool { + // account_id: 123, + // balance: 0, + // total_earnings: 0, + // points: 0 + // }; + + // Balances::make_free_balance_be(100); + // let pool = pool.update_total_earnings_and_balance(); + // assert_eq!( + // pool.total_earnings, 100 + // ); + // assert_eq!( + // pool.balance, 100 + // ); + + // let pool = pool.update_total_earnings_and_balance(); + // }); + // } } mod unbond_pool { @@ -332,51 +354,51 @@ mod claim_payout { let reward_pool = RewardPools::::get(0).unwrap(); let delegator = Delegators::::get(10).unwrap(); - // given no rewards have been earned - // when + // Given no rewards have been earned + // When let (reward_pool, delegator, payout) = Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator).unwrap(); - // then + // Then assert_eq!(payout, 0); assert_eq!(delegator, del(0)); assert_eq!(reward_pool, rew(0, 0, 0)); - // given the pool has earned some rewards for the first time - Balances::make_free_balance_be(&REWARDS_ACCOUNT, 2); + // Given the pool has earned some rewards for the first time + Balances::make_free_balance_be(&reward_pool.account_id, 5); - // when + // When let (reward_pool, delegator, payout) = Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator).unwrap(); - // then - assert_eq!(payout, 2); - assert_eq!(reward_pool, rew(0, 0, 2)); - assert_eq!(delegator, del(2)); + // Then + assert_eq!(payout, 5); // (10 * 5 del virtual points / 10 * 5 pool points) * 5 pool balance + assert_eq!(reward_pool, rew(0, 0, 5)); + assert_eq!(delegator, del(5)); - // given the pool has earned rewards again - Balances::make_free_balance_be(&REWARDS_ACCOUNT, 4); + // Given the pool has earned rewards again + Balances::make_free_balance_be(&REWARDS_ACCOUNT, 10); - // when + // When let (reward_pool, delegator, payout) = Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator).unwrap(); - // then - assert_eq!(payout, 4); - assert_eq!(reward_pool, rew(0, 0, 6)); - assert_eq!(delegator, del(6)); + // Then + assert_eq!(payout, 10); // (10 * 10 del virtual points / 10 pool points) * 5 pool balance + assert_eq!(reward_pool, rew(0, 0, 15)); + assert_eq!(delegator, del(15)); - // given the pool has earned no new rewards + // Given the pool has earned no new rewards Balances::make_free_balance_be(&REWARDS_ACCOUNT, 0); - // when + // When let (reward_pool, delegator, payout) = Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator).unwrap(); - // then + // Then assert_eq!(payout, 0); - assert_eq!(reward_pool, rew(0, 0, 6)); - assert_eq!(delegator, del(6)); + assert_eq!(reward_pool, rew(0, 0, 15)); + assert_eq!(delegator, del(15)); }); } @@ -430,31 +452,30 @@ mod claim_payout { Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_40).unwrap(); // Then - // The exact math is (400/900) * 90, so ideally this should be 40. But given 400 / - // 900 (del virtual points / pool points) = ~0.444, it gets rounded down. - assert_eq!(payout, 39); + assert_eq!(payout, 40); // (400 del virtual points / 900 pool points) * 90 pool balance assert_eq!(del_40, del(40, 100)); assert_eq!( reward_pool, rew( - 51, - 9_000 - 100 * 40, // old pool points - delegator virtual points + 50, + // old pool points - delegator virtual points + 9_000 - 100 * 40, 100 ) ); // Mock the reward pool transferring the payout to del_40 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 39)); + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 40)); // When let (reward_pool, del_50, payout) = Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_50).unwrap(); // Then - assert_eq!(payout, 51); // (50 del virtual points / 50 pool points) * 51 pool balance + assert_eq!(payout, 50); // (50 del virtual points / 50 pool points) * 50 pool balance assert_eq!(del_50, del(50, 100)); assert_eq!(reward_pool, rew(0, 0, 100)); // Mock the reward pool transferring the payout to del_50 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 51)); + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 50)); // Given the reward pool has some new rewards Balances::make_free_balance_be(&REWARDS_ACCOUNT, 50); @@ -475,26 +496,26 @@ mod claim_payout { Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_40).unwrap(); // Then - assert_eq!(payout, 19); // (2,000 del virtual points / 4,500 pool points) * 45 pool balance + assert_eq!(payout, 20); // (2,000 del virtual points / 4,500 pool points) * 45 pool balance assert_eq!(del_40, del(40, 150)); - assert_eq!(reward_pool, rew(26, 4_500 - 50 * 40, 150)); - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 19)); + assert_eq!(reward_pool, rew(25, 4_500 - 50 * 40, 150)); + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 20)); // Given del_50 hasn't claimed and the reward pools has just earned 50 assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 50)); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 76); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 75); // When let (reward_pool, del_50, payout) = Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_50).unwrap(); // Then - assert_eq!(payout, 50); // (5,000 del virtual points / 7,5000 pool points) * 76 pool balance + assert_eq!(payout, 50); // (5,000 del virtual points / 7,5000 pool points) * 75 pool balance assert_eq!(del_50, del(50, 200)); assert_eq!( reward_pool, rew( - 26, + 25, // old pool points + points from new earnings - del points. // // points from new earnings = new earnings(50) * primary_pool.points(100) @@ -513,13 +534,13 @@ mod claim_payout { // Then assert_eq!(payout, 5); assert_eq!(del_10, del(10, 200)); - assert_eq!(reward_pool, rew(21, 2_500 - 10 * 50, 200)); + assert_eq!(reward_pool, rew(20, 2_500 - 10 * 50, 200)); // Mock the reward pool transferring the payout to del_10 assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 5)); // Given del_40 hasn't claimed and the reward pool has just earned 400 assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 400)); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 421); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 420); // When let (reward_pool, del_10, payout) = @@ -531,7 +552,7 @@ mod claim_payout { assert_eq!( reward_pool, rew( - 381, + 380, // old pool points + points from new earnings - del points // // points from new earnings = new earnings(400) * primary_pool.points(100) @@ -545,16 +566,16 @@ mod claim_payout { // Given del_40 + del_50 haven't claimed and the reward pool has earned 20 assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 20)); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 401); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 400); // When let (reward_pool, del_10, payout) = Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_10).unwrap(); // Then - assert_eq!(payout, 2); // (200 del virtual points / 39,800 pool points) * 401 + assert_eq!(payout, 2); // (200 del virtual points / 38,000 pool points) * 400 pool balance assert_eq!(del_10, del(10, 620)); - assert_eq!(reward_pool, rew(399, (38_000 + 20 * 100) - 10 * 20, 620)); + assert_eq!(reward_pool, rew(398, (38_000 + 20 * 100) - 10 * 20, 620)); // Mock the reward pool transferring the payout to del_10 assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 2)); @@ -563,9 +584,9 @@ mod claim_payout { Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_40).unwrap(); // Then - assert_eq!(payout, 188); // (18,800 del virtual points / 39,800 pool points) * 399 + assert_eq!(payout, 188); // (18,800 del virtual points / 39,800 pool points) * 399 pool balance assert_eq!(del_40, del(40, 620)); - assert_eq!(reward_pool, rew(211, 39_800 - 40 * 470, 620)); + assert_eq!(reward_pool, rew(210, 39_800 - 40 * 470, 620)); // Mock the reward pool transferring the payout to del_10 assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 188)); @@ -574,7 +595,7 @@ mod claim_payout { Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_50).unwrap(); // Then - assert_eq!(payout, 211); // (21,000 / 21,000) * 211 + assert_eq!(payout, 210); // (21,000 / 21,000) * 210 assert_eq!(del_50, del(50, 620)); assert_eq!(reward_pool, rew(0, 21_000 - 50 * 420, 620)); }); diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index b6cf21ad35747..66159492e6647 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -548,6 +548,8 @@ where } } + // TODO look into how to decouple this + // move sensitive logic here /// Slash a pool account fn pool_slash( &mut self, From cc388e76223765773621c4779d2c474f919aa0fa Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 21 Jan 2022 15:05:12 -0800 Subject: [PATCH 024/299] Do not use FixedU128 in balance_to_unbond or points_to_issue --- frame/pools/src/lib.rs | 32 ++++++++++++-------------------- frame/pools/src/tests.rs | 4 ++++ 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 1031d4b19e9f5..e7f5257b95f8e 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -85,19 +85,15 @@ fn points_to_issue( (true, false) => { // The pool was totally slashed. - // This is the equivalent of `(current_points / 1) * new_funds`. This is more favorable - // to the joiner then simply returning `new_funds`. + // This is the equivalent of `(current_points / 1) * new_funds`. new_funds.saturating_mul(current_points) }, (false, false) => { - // REMINDER: `saturating_from_rational` panics if denominator is zero - let points_per_balance = FixedU128::saturating_from_rational( - T::BalanceToU128::convert(current_points), - T::BalanceToU128::convert(current_balance), - ); - let new_funds = T::BalanceToU128::convert(new_funds); - - T::U128ToBalance::convert(points_per_balance.saturating_mul_int(new_funds)) + // Equivalent to (current_points / current_balance) * new_funds + current_points + .saturating_mul(new_funds) + // We check for zero above + .div(current_balance) }, } } @@ -114,14 +110,11 @@ fn balance_to_unbond( return Zero::zero() } - // REMINDER: `saturating_from_rational` panics if denominator is zero - let balance_per_share = FixedU128::saturating_from_rational( - T::BalanceToU128::convert(current_balance), - T::BalanceToU128::convert(current_points), - ); - let delegator_points = T::BalanceToU128::convert(delegator_points); - - T::U128ToBalance::convert(balance_per_share.saturating_mul_int(delegator_points)) + // Equivalent of (current_balance / current_points) * delegator_points + current_balance + .saturating_mul(delegator_points) + // We check for zero above + .div(current_points) } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] @@ -759,8 +752,7 @@ impl Pallet { let delegator_payout = if delegator_virtual_points.is_zero() || current_points.is_zero() { BalanceOf::::zero() } else { - // `(delegator_virtual_points * reward_pool.balance) / current_points` is equivalent to - // `(delegator_virtual_points / current_points) * reward_pool.balance` + // Equivalent to `(delegator_virtual_points / current_points) * reward_pool.balance` let payout = delegator_virtual_points .saturating_mul(reward_pool.balance) // We check for zero above diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 7f0459a814286..99d1ebe4d8a17 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -59,6 +59,8 @@ mod points_to_issue { assert_eq!(points_to_issue(30, 100, 10), 33); // 2 points : 3 balance ratio assert_eq!(points_to_issue(300, 200, 10), 6); + // 4 points : 9 balance ratio + assert_eq!(points_to_issue(900, 400, 90), 40) }); } } @@ -84,6 +86,8 @@ mod balance_to_unbond { assert_eq!(balance_to_unbond(100, 30, 10), 33); // 2 balance : 3 points ratio assert_eq!(balance_to_unbond(200, 300, 10), 6); + // 4 balance : 9 points ratio + assert_eq!(balance_to_unbond(400, 900, 90), 40) }); } } From 6852c6f8eadf3bb823b7b44c5cb36219f88ba37a Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 21 Jan 2022 15:22:41 -0800 Subject: [PATCH 025/299] Do not use FixedU128 do_slash --- frame/pools/src/lib.rs | 48 +++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index e7f5257b95f8e..8160f8ef637f4 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -163,16 +163,18 @@ impl PrimaryPool { let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); - let points_to_balance_ratio = FixedU128::saturating_from_rational( - T::BalanceToU128::convert(self.points), - T::BalanceToU128::convert(bonded_balance), - ); + let points_to_balance_ratio_floor = self + .points + // We checked for zero above + .div(bonded_balance); + // TODO make sure these checks make sense. Taken from staking design chat with Al + // Pool points can inflate relative to balance, but only if the pool is slashed. // // If we cap the ratio of points:balance so one cannot join a pool that has been slashed // 90%, - ensure!(points_to_balance_ratio < 10.into(), Error::::OverflowRisk); + ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( new_funds.saturating_add(bonded_balance) < @@ -841,32 +843,25 @@ impl Pallet { }); let total_affected_balance = bonded_balance.saturating_add(unbonding_affected_balance); + if total_affected_balance.is_zero() { + return Some((Zero::zero(), Default::default())) + } if slash_amount > total_affected_balance { // TODO this shouldn't happen as long as MaxBonding pools is greater thant the slash // defer duration, which it should implicitly be because we expect it be longer then the // UnbondindDuration. TODO clearly document these assumptions }; - // Panics if denominator is zero - let slash_ratio = if total_affected_balance <= Zero::zero() { - return Some((Zero::zero(), Default::default())) - } else { - // REMINDER: `saturating_from_rational` panics if denominator is zero - FixedU128::saturating_from_rational( - T::BalanceToU128::convert(slash_amount), - T::BalanceToU128::convert(total_affected_balance), - ) - }; - - let slash_multiplier = FixedU128::one().saturating_sub(slash_ratio); - let unlock_chunk_balances: BTreeMap<_, _> = affected_range .filter_map(|era| { if let Some(mut unbond_pool) = sub_pools.with_era.get_mut(&era) { - let pre_slash_balance = T::BalanceToU128::convert(unbond_pool.balance); - let after_slash_balance = T::U128ToBalance::convert( - slash_multiplier.saturating_mul_int(pre_slash_balance), - ); + // Equivalent to `(slash_amount / total_affected_balance) * unbond_pool.balance` + let pool_slash_amount = slash_amount + .saturating_mul(unbond_pool.balance) + // We check for zero above + .div(total_affected_balance); + let after_slash_balance = unbond_pool.balance.saturating_sub(pool_slash_amount); + unbond_pool.balance = after_slash_balance; Some((era, after_slash_balance)) @@ -878,10 +873,11 @@ impl Pallet { SubPoolsStorage::::insert(pool_id, sub_pools); - let slashed_bonded_pool_balance = { - let pre_slash_balance = T::BalanceToU128::convert(bonded_balance); - T::U128ToBalance::convert(slash_multiplier.saturating_mul_int(pre_slash_balance)) - }; + // Equivalent to `(slash_amount / total_affected_balance) * bonded_balance` + let slashed_bonded_pool_balance = slash_amount + .saturating_mul(bonded_balance) + // We check for zero above + .div(total_affected_balance); Some((slashed_bonded_pool_balance, unlock_chunk_balances)) } From bab05ac57b194c9a40f606800a940725d7970b75 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 21 Jan 2022 16:23:05 -0800 Subject: [PATCH 026/299] Use a u256 for RewardPoints --- frame/pools/src/lib.rs | 52 ++++++++++++++++++---------------------- frame/pools/src/mock.rs | 24 ++++++++----------- frame/pools/src/tests.rs | 12 +++++----- frame/staking/src/lib.rs | 2 +- 4 files changed, 40 insertions(+), 50 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 8160f8ef637f4..86dbae08b2119 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -37,10 +37,8 @@ use frame_support::{ DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; -use sp_arithmetic::{FixedPointNumber, FixedU128}; -use sp_runtime::traits::{ - Bounded, Convert, One, Saturating, StaticLookup, TrailingZeroInput, Zero, -}; +use sp_core::U256; +use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZeroInput, Zero}; use sp_staking::{EraIndex, PoolsInterface, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div}; @@ -68,7 +66,7 @@ type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type SubPoolsWithEra = BoundedBTreeMap, ::MaxUnbonding>; // NOTE: this assumes the balance type u128 or smaller. -// type RewardPoints = u256; +type RewardPoints = U256; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; @@ -204,8 +202,7 @@ pub struct RewardPool { /// this type should be bigger than `Balance`. total_earnings: BalanceOf, /// The total points of this reward pool after the last claimed payout. - // points: RewardPoints, - points: BalanceOf, + points: RewardPoints, } impl RewardPool { @@ -314,17 +311,11 @@ pub mod pallet { /// The nominating balance. type Currency: Currency; - // Infallible method for converting `Currency::Balance` to `u128`. - type BalanceToU128: Convert, u128>; - - // Infallible method for converting `u128` to `Currency::Balance`. - type U128ToBalance: Convert>; - - // Infallible method for converting `Currency::Balance` to `RewardPoints`. - // type BalanceToReward: Convert, RewardPoints>; + // Infallible method for converting `Currency::Balance` to `U256`. + type BalanceToU256: Convert, U256>; - // Infallible method for converting `RewardPoints` to `Currency::Balance`. - // type RewardToBalance: Convert>; + // Infallible method for converting `U256` to `Currency::Balance`. + type U256ToBalance: Convert>; /// The interface for nominating. type StakingInterface: StakingInterface< @@ -676,7 +667,7 @@ pub mod pallet { id, RewardPool:: { balance: Zero::zero(), - points: Zero::zero(), + points: U256::zero(), total_earnings: Zero::zero(), account_id: reward_dest, }, @@ -730,14 +721,17 @@ impl Pallet { let last_total_earnings = reward_pool.total_earnings; let mut reward_pool = reward_pool.update_total_earnings_and_balance(); // Notice there is an edge case where total_earnings have not increased and this is zero - let new_earnings = reward_pool.total_earnings.saturating_sub(last_total_earnings); + let new_earnings = T::BalanceToU256::convert( + reward_pool.total_earnings.saturating_sub(last_total_earnings), + ); // The new points that will be added to the pool. For every unit of balance that has // been earned by the reward pool, we inflate the reward pool points by // `primary_pool.total_points`. In effect this allows each, single unit of balance (e.g. // plank) to be divvied up pro-rata among delegators based on points. // TODO this needs to be some sort of BigUInt arithmetic - let new_points = primary_pool.points.saturating_mul(new_earnings); + let new_points = + T::BalanceToU256::convert(primary_pool.points).saturating_mul(new_earnings); // The points of the reward pool after taking into account the new earnings. Notice that // this only stays even or increases over time except for when we subtract delegator virtual @@ -748,19 +742,19 @@ impl Pallet { let new_earnings_since_last_claim = reward_pool.total_earnings.saturating_sub(delegator.reward_pool_total_earnings); // The points of the reward pool that belong to the delegator. - let delegator_virtual_points = - delegator.points.saturating_mul(new_earnings_since_last_claim); + let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) + .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); let delegator_payout = if delegator_virtual_points.is_zero() || current_points.is_zero() { - BalanceOf::::zero() + Zero::zero() } else { // Equivalent to `(delegator_virtual_points / current_points) * reward_pool.balance` - let payout = delegator_virtual_points - .saturating_mul(reward_pool.balance) - // We check for zero above - .div(current_points); - - payout + T::U256ToBalance::convert( + delegator_virtual_points + .saturating_mul(T::BalanceToU256::convert(reward_pool.balance)) + // We check for zero above + .div(current_points), + ) }; // Record updates diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 1406d41435dd0..43c719e40acea 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -118,21 +118,17 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } -pub struct BalanceToU128; -impl Convert for BalanceToU128 { - fn convert(n: Balance) -> u128 { - n as u128 +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> U256 { + n.into() } } -pub struct U128ToBalance; -impl Convert for U128ToBalance { - fn convert(n: u128) -> Balance { - if n > Balance::MAX as u128 { - Balance::MAX - } else { - n as Balance - } +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: U256) -> Balance { + n.try_into().unwrap() } } @@ -143,8 +139,8 @@ parameter_types! { impl pools::Config for Runtime { type Event = Event; type Currency = Balances; - type BalanceToU128 = BalanceToU128; - type U128ToBalance = U128ToBalance; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; type StakingInterface = StakingMock; type MaxUnbonding = MaxUnbonding; } diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 99d1ebe4d8a17..60710a2dc266b 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -21,7 +21,7 @@ fn test_setup_works() { RewardPools::::get(0).unwrap(), RewardPool:: { balance: 0, - points: 0, + points: 0.into(), total_earnings: 0, account_id: REWARDS_ACCOUNT } @@ -310,7 +310,7 @@ mod join { 1, RewardPool:: { balance: Zero::zero(), - points: Zero::zero(), + points: U256::zero(), total_earnings: Zero::zero(), account_id: 321, }, @@ -341,9 +341,9 @@ mod claim_payout { #[test] fn calculate_delegator_payout_works_with_a_pool_of_1() { - let rew = |balance, points, total_earnings| RewardPool:: { + let rew = |balance, points: u32, total_earnings| RewardPool:: { balance, - points, + points: points.into(), total_earnings, account_id: REWARDS_ACCOUNT, }; @@ -408,9 +408,9 @@ mod claim_payout { #[test] fn calculate_delegator_payout_works_with_a_pool_of_3() { - let rew = |balance, points, total_earnings| RewardPool:: { + let rew = |balance, points: u32, total_earnings| RewardPool:: { balance, - points, + points: points.into(), total_earnings, account_id: REWARDS_ACCOUNT, }; diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 66159492e6647..8cd4fd6cd80b7 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -549,7 +549,7 @@ where } // TODO look into how to decouple this - // move sensitive logic here + // move sensitive logic /// Slash a pool account fn pool_slash( &mut self, From 92f67630ee657ead6af9378c29260ebfb29f34ba Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 21 Jan 2022 16:33:31 -0800 Subject: [PATCH 027/299] Rename storage maps with Storage suffix --- frame/pools/src/lib.rs | 122 +++++++++++++++++++-------------------- frame/pools/src/tests.rs | 52 ++++++++--------- 2 files changed, 87 insertions(+), 87 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 86dbae08b2119..559c93c62a63c 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -2,7 +2,7 @@ //! //! The delegation pool abstraction is concretely composed of: //! -//! * primary pool: This pool represents the actively staked funds ... +//! * bonded pool: This pool represents the actively staked funds ... //! * rewards pool: The rewards earned by actively staked funds. Delegator can withdraw rewards once //! * sub pools: This a group of pools where we have a set of pools organized by era //! (`SubPools.with_era`) and one pool that is not associated with an era (`SubPools.no_era`). @@ -121,7 +121,7 @@ fn balance_to_unbond( #[scale_info(skip_type_params(T))] pub struct Delegator { pool: PoolId, - /// The quantity of points this delegator has in the primary pool or in a sub pool if + /// The quantity of points this delegator has in the bonded pool or in a sub pool if /// `Self::unbonding_era` is some. points: BalanceOf, /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. @@ -137,13 +137,13 @@ pub struct Delegator { #[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] -pub struct PrimaryPool { +pub struct BondedPool { points: BalanceOf, // The _Stash_ and _Controller_ account for the pool. account_id: T::AccountId, } -impl PrimaryPool { +impl BondedPool { /// Get the amount of points to issue for some new funds that will be bonded in the pool. fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); @@ -333,7 +333,7 @@ pub mod pallet { /// Active delegators. #[pallet::storage] - pub(crate) type Delegators = + pub(crate) type DelegatorStorage = CountedStorageMap<_, Twox64Concat, T::AccountId, Delegator>; /// `PoolId` lookup from the pool's `AccountId`. Useful for pool lookup from the slashing @@ -343,16 +343,16 @@ pub mod pallet { /// Bonded pools. #[pallet::storage] - pub(crate) type PrimaryPools = - CountedStorageMap<_, Twox64Concat, PoolId, PrimaryPool>; + pub(crate) type BondedPoolStorage = + CountedStorageMap<_, Twox64Concat, PoolId, BondedPool>; /// Reward pools. This is where there rewards for each pool accumulate. When a delegators payout /// is claimed, the balance comes out fo the reward pool. #[pallet::storage] - pub(crate) type RewardPools = + pub(crate) type RewardPoolStorage = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool>; - /// Groups of unbonding pools. Each group of unbonding pools belongs to a primary pool, + /// Groups of unbonding pools. Each group of unbonding pools belongs to a bonded pool, /// hence the name sub-pools. #[pallet::storage] pub(crate) type SubPoolsStorage = @@ -370,7 +370,7 @@ pub mod pallet { #[pallet::error] #[cfg_attr(test, derive(PartialEq))] pub enum Error { - /// The given (primary) pool id does not exist. + /// The given (bonded) pool id does not exist. PoolNotFound, /// The given account is not a delegator. DelegatorNotFound, @@ -409,32 +409,32 @@ pub mod pallet { pub fn join(origin: OriginFor, amount: BalanceOf, target: PoolId) -> DispatchResult { let who = ensure_signed(origin)?; // If a delegator already exists that means they already belong to a pool - ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); + ensure!(!DelegatorStorage::::contains_key(&who), Error::::AccountBelongsToOtherPool); - let mut primary_pool = - PrimaryPools::::get(target).ok_or(Error::::PoolNotFound)?; - primary_pool.ok_to_join_with(amount)?; + let mut bonded_pool = + BondedPoolStorage::::get(target).ok_or(Error::::PoolNotFound)?; + bonded_pool.ok_to_join_with(amount)?; // The pool should always be created in such a way its in a state to bond extra, but if // the active balance is slashed below the minimum bonded or the account cannot be // found, we exit early. - if !T::StakingInterface::can_bond_extra(&primary_pool.account_id, amount) { + if !T::StakingInterface::can_bond_extra(&bonded_pool.account_id, amount) { return Err(Error::::StakingError.into()) } // We don't actually care about writing the reward pool, we just need its // total earnings at this point in time. - let reward_pool = RewardPools::::get(target) + let reward_pool = RewardPoolStorage::::get(target) .ok_or(Error::::RewardPoolNotFound)? // This is important because we want the most up-to-date total earnings. .update_total_earnings_and_balance(); - let old_free_balance = T::Currency::free_balance(&primary_pool.account_id); + let old_free_balance = T::Currency::free_balance(&bonded_pool.account_id); // Transfer the funds to be bonded from `who` to the pools account so the pool can then // go bond them. T::Currency::transfer( &who, - &primary_pool.account_id, + &bonded_pool.account_id, amount, ExistenceRequirement::KeepAlive, )?; @@ -442,17 +442,17 @@ pub mod pallet { // Try not to bail after the above transfer // This should now include the transferred balance. - let new_free_balance = T::Currency::free_balance(&primary_pool.account_id); + let new_free_balance = T::Currency::free_balance(&bonded_pool.account_id); // Get the exact amount we can bond extra. let exact_amount_to_bond = new_free_balance.saturating_sub(old_free_balance); // We must calculate the points to issue *before* we bond `who`'s funds, else the // points:balance ratio will be wrong. - let new_points = primary_pool.points_to_issue(exact_amount_to_bond); - primary_pool.points = primary_pool.points.saturating_add(new_points); + let new_points = bonded_pool.points_to_issue(exact_amount_to_bond); + bonded_pool.points = bonded_pool.points.saturating_add(new_points); - T::StakingInterface::bond_extra(&primary_pool.account_id, exact_amount_to_bond)?; + T::StakingInterface::bond_extra(&bonded_pool.account_id, exact_amount_to_bond)?; - Delegators::insert( + DelegatorStorage::insert( who.clone(), Delegator:: { pool: target, @@ -468,7 +468,7 @@ pub mod pallet { unbonding_era: None, }, ); - PrimaryPools::insert(target, primary_pool); + BondedPoolStorage::insert(target, bonded_pool); Self::deposit_event(Event::::Joined { delegator: who, @@ -487,17 +487,17 @@ pub mod pallet { #[pallet::weight(666)] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; - let primary_pool = PrimaryPools::::get(&delegator.pool).ok_or_else(|| { - log!(error, "A primary pool could not be found, this is a system logic error."); + let delegator = DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let bonded_pool = BondedPoolStorage::::get(&delegator.pool).ok_or_else(|| { + log!(error, "A bonded pool could not be found, this is a system logic error."); debug_assert!( false, - "A primary pool could not be found, this is a system logic error." + "A bonded pool could not be found, this is a system logic error." ); Error::::PoolNotFound })?; - Self::do_reward_payout(who, delegator, &primary_pool)?; + Self::do_reward_payout(who, delegator, &bonded_pool)?; Ok(()) } @@ -507,29 +507,29 @@ pub mod pallet { #[pallet::weight(666)] pub fn unbond(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; - let mut primary_pool = - PrimaryPools::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; + let delegator = DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let mut bonded_pool = + BondedPoolStorage::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; // Claim the the payout prior to unbonding. Once the user is unbonding their points - // no longer exist in the primary pool and thus they can no longer claim their payouts. + // no longer exist in the bonded pool and thus they can no longer claim their payouts. // It is not strictly necessary to claim the rewards, but we do it here for UX. - Self::do_reward_payout(who.clone(), delegator, &primary_pool)?; + Self::do_reward_payout(who.clone(), delegator, &bonded_pool)?; // Re-fetch the delegator because they where updated by `do_reward_payout`. - let mut delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let mut delegator = DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPoolsStorage::::get(delegator.pool).unwrap_or_default(); let current_era = T::StakingInterface::current_era(); - let balance_to_unbond = primary_pool.balance_to_unbond(delegator.points); + let balance_to_unbond = bonded_pool.balance_to_unbond(delegator.points); - // Update the primary pool. Note that we must do this *after* calculating the balance + // Update the bonded pool. Note that we must do this *after* calculating the balance // to unbond so we have the correct points for the balance:share ratio. - primary_pool.points = primary_pool.points.saturating_sub(delegator.points); + bonded_pool.points = bonded_pool.points.saturating_sub(delegator.points); // Unbond in the actual underlying pool - T::StakingInterface::unbond(&primary_pool.account_id, balance_to_unbond)?; + T::StakingInterface::unbond(&bonded_pool.account_id, balance_to_unbond)?; // Merge any older pools into the general, era agnostic unbond pool. Note that we do // this before inserting to ensure we don't go over the max unbonding pools. @@ -555,9 +555,9 @@ pub mod pallet { }); // Now that we know everything has worked write the items to storage. - PrimaryPools::insert(delegator.pool, primary_pool); + BondedPoolStorage::insert(delegator.pool, bonded_pool); SubPoolsStorage::insert(delegator.pool, sub_pools); - Delegators::insert(who, delegator); + DelegatorStorage::insert(who, delegator); Ok(()) } @@ -568,7 +568,7 @@ pub mod pallet { #[pallet::weight(666)] pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let delegator = Delegators::::take(&who).ok_or(Error::::DelegatorNotFound)?; + let delegator = DelegatorStorage::::take(&who).ok_or(Error::::DelegatorNotFound)?; let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); @@ -596,10 +596,10 @@ pub mod pallet { balance_to_unbond }; - let primary_pool = - PrimaryPools::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; + let bonded_pool = + BondedPoolStorage::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; T::Currency::transfer( - &primary_pool.account_id, + &bonded_pool.account_id, &who, balance_to_unbond, ExistenceRequirement::AllowDeath, @@ -625,7 +625,7 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(!PrimaryPools::::contains_key(id), Error::::IdInUse); + ensure!(!BondedPoolStorage::::contains_key(id), Error::::IdInUse); ensure!(amount >= T::StakingInterface::minimum_bond(), Error::::MinimiumBondNotMet); // TODO create can_* fns so we can bail in the beggining if some pre-conditions are not // met T::StakingInterface::can_bond() @@ -635,13 +635,13 @@ pub mod pallet { T::Currency::transfer(&who, &stash, amount, ExistenceRequirement::AllowDeath)?; - let mut primary_pool = - PrimaryPool:: { points: Zero::zero(), account_id: stash.clone() }; + let mut bonded_pool = + BondedPool:: { points: Zero::zero(), account_id: stash.clone() }; // We must calculate the points to issue *before* we bond who's funds, else // points:balance ratio will be wrong. - let points_to_issue = primary_pool.points_to_issue(amount); - primary_pool.points = points_to_issue; + let points_to_issue = bonded_pool.points_to_issue(amount); + bonded_pool.points = points_to_issue; T::StakingInterface::bond( stash.clone(), @@ -653,7 +653,7 @@ pub mod pallet { T::StakingInterface::nominate(stash.clone(), targets)?; - Delegators::::insert( + DelegatorStorage::::insert( who, Delegator:: { pool: id, @@ -662,8 +662,8 @@ pub mod pallet { unbonding_era: None, }, ); - PrimaryPools::::insert(id, primary_pool); - RewardPools::::insert( + BondedPoolStorage::::insert(id, bonded_pool); + RewardPoolStorage::::insert( id, RewardPool:: { balance: Zero::zero(), @@ -710,7 +710,7 @@ impl Pallet { /// Calculate the rewards for `delegator`. fn calculate_delegator_payout( - primary_pool: &PrimaryPool, + bonded_pool: &BondedPool, reward_pool: RewardPool, mut delegator: Delegator, ) -> Result<(RewardPool, Delegator, BalanceOf), DispatchError> { @@ -727,11 +727,11 @@ impl Pallet { // The new points that will be added to the pool. For every unit of balance that has // been earned by the reward pool, we inflate the reward pool points by - // `primary_pool.total_points`. In effect this allows each, single unit of balance (e.g. + // `bonded_pool.total_points`. In effect this allows each, single unit of balance (e.g. // plank) to be divvied up pro-rata among delegators based on points. // TODO this needs to be some sort of BigUInt arithmetic let new_points = - T::BalanceToU256::convert(primary_pool.points).saturating_mul(new_earnings); + T::BalanceToU256::convert(bonded_pool.points).saturating_mul(new_earnings); // The points of the reward pool after taking into account the new earnings. Notice that // this only stays even or increases over time except for when we subtract delegator virtual @@ -781,16 +781,16 @@ impl Pallet { fn do_reward_payout( delegator_id: T::AccountId, delegator: Delegator, - primary_pool: &PrimaryPool, + bonded_pool: &BondedPool, ) -> DispatchResult { - let reward_pool = RewardPools::::get(&delegator.pool).ok_or_else(|| { + let reward_pool = RewardPoolStorage::::get(&delegator.pool).ok_or_else(|| { log!(error, "A reward pool could not be found, this is a system logic error."); debug_assert!(false, "A reward pool could not be found, this is a system logic error."); Error::::RewardPoolNotFound })?; let (reward_pool, delegator, delegator_payout) = - Self::calculate_delegator_payout(primary_pool, reward_pool, delegator)?; + Self::calculate_delegator_payout(bonded_pool, reward_pool, delegator)?; // Transfer payout to the delegator. Self::transfer_reward( @@ -801,8 +801,8 @@ impl Pallet { )?; // Write the updated delegator and reward pool to storage - RewardPools::insert(delegator.pool, reward_pool); - Delegators::insert(delegator_id, delegator); + RewardPoolStorage::insert(delegator.pool, reward_pool); + DelegatorStorage::insert(delegator_id, delegator); Ok(()) } diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 60710a2dc266b..5842aa53d5e16 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -8,17 +8,17 @@ use frame_support::{assert_noop, assert_ok}; #[test] fn test_setup_works() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(PrimaryPools::::count(), 1); - assert_eq!(RewardPools::::count(), 1); + assert_eq!(BondedPoolStorage::::count(), 1); + assert_eq!(RewardPoolStorage::::count(), 1); assert_eq!(SubPoolsStorage::::count(), 0); - assert_eq!(Delegators::::count(), 1); + assert_eq!(DelegatorStorage::::count(), 1); assert_eq!( - PrimaryPools::::get(0).unwrap(), - PrimaryPool:: { points: 10, account_id: PRIMARY_ACCOUNT } + BondedPoolStorage::::get(0).unwrap(), + BondedPool:: { points: 10, account_id: PRIMARY_ACCOUNT } ); assert_eq!( - RewardPools::::get(0).unwrap(), + RewardPoolStorage::::get(0).unwrap(), RewardPool:: { balance: 0, points: 0.into(), @@ -27,7 +27,7 @@ fn test_setup_works() { } ); assert_eq!( - Delegators::::get(10).unwrap(), + DelegatorStorage::::get(10).unwrap(), Delegator:: { pool: 0, points: 10, @@ -105,7 +105,7 @@ mod primary_pool { #[test] fn ok_to_join_with_works() { ExtBuilder::default().build_and_execute(|| { - let pool = PrimaryPool:: { points: 100, account_id: 123 }; + let pool = BondedPool:: { points: 100, account_id: 123 }; // Simulate a 100% slashed pool StakingMock::set_bonded_balance(123, 0); @@ -246,13 +246,13 @@ mod join { ExtBuilder::default().build_and_execute(|| { Balances::make_free_balance_be(&11, 5 + 2); - assert!(!Delegators::::contains_key(&11)); + assert!(!DelegatorStorage::::contains_key(&11)); assert_ok!(Pools::join(Origin::signed(11), 2, 0)); // Storage is updated correctly assert_eq!( - Delegators::::get(&11).unwrap(), + DelegatorStorage::::get(&11).unwrap(), Delegator:: { pool: 0, points: 2, @@ -261,8 +261,8 @@ mod join { } ); assert_eq!( - PrimaryPools::::get(&0).unwrap(), - PrimaryPool:: { points: 12, account_id: PRIMARY_ACCOUNT } + BondedPoolStorage::::get(&0).unwrap(), + BondedPool:: { points: 12, account_id: PRIMARY_ACCOUNT } ); }); } @@ -282,9 +282,9 @@ mod join { StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 0); assert_noop!(Pools::join(Origin::signed(11), 420, 0), Error::::OverflowRisk); - PrimaryPools::::insert( + BondedPoolStorage::::insert( 1, - PrimaryPool:: { points: 100, account_id: 123 }, + BondedPool:: { points: 100, account_id: 123 }, ); // Force the points:balance ratio to 100/10 (so 10) StakingMock::set_bonded_balance(123, 10); @@ -306,7 +306,7 @@ mod join { Pools::join(Origin::signed(11), 420, 1), Error::::RewardPoolNotFound ); - RewardPools::::insert( + RewardPoolStorage::::insert( 1, RewardPool:: { balance: Zero::zero(), @@ -327,9 +327,9 @@ mod claim_payout { #[test] fn calculate_delegator_payout_errors_if_a_delegator_is_unbonding() { ExtBuilder::default().build_and_execute(|| { - let primary_pool = PrimaryPools::::get(0).unwrap(); - let reward_pool = RewardPools::::get(0).unwrap(); - let mut delegator = Delegators::::get(10).unwrap(); + let primary_pool = BondedPoolStorage::::get(0).unwrap(); + let reward_pool = RewardPoolStorage::::get(0).unwrap(); + let mut delegator = DelegatorStorage::::get(10).unwrap(); delegator.unbonding_era = Some(0); assert_noop!( @@ -354,9 +354,9 @@ mod claim_payout { unbonding_era: None, }; ExtBuilder::default().build_and_execute(|| { - let primary_pool = PrimaryPools::::get(0).unwrap(); - let reward_pool = RewardPools::::get(0).unwrap(); - let delegator = Delegators::::get(10).unwrap(); + let primary_pool = BondedPoolStorage::::get(0).unwrap(); + let reward_pool = RewardPoolStorage::::get(0).unwrap(); + let delegator = DelegatorStorage::::get(10).unwrap(); // Given no rewards have been earned // When @@ -424,16 +424,16 @@ mod claim_payout { ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - let mut primary_pool = PrimaryPools::::get(0).unwrap(); + let mut primary_pool = BondedPoolStorage::::get(0).unwrap(); primary_pool.points = 100; - let reward_pool = RewardPools::::get(0).unwrap(); + let reward_pool = RewardPoolStorage::::get(0).unwrap(); // Delegator with 10 points - let del_10 = Delegators::::get(10).unwrap(); + let del_10 = DelegatorStorage::::get(10).unwrap(); // Delegator with 40 points - let del_40 = Delegators::::get(40).unwrap(); + let del_40 = DelegatorStorage::::get(40).unwrap(); // Delegator with 50 points - let del_50 = Delegators::::get(50).unwrap(); + let del_50 = DelegatorStorage::::get(50).unwrap(); // Given we have a total of 100 points split among the delegators assert_eq!(del_50.points + del_40.points + del_10.points, 100); From a20a0166472b2b24642c20af3e226215d9d8f94e Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 24 Jan 2022 11:12:15 -0800 Subject: [PATCH 028/299] wip --- frame/pools/src/lib.rs | 32 +++++--- frame/pools/src/mock.rs | 24 +++++- frame/pools/src/tests.rs | 156 +++++++++++++++++++++++++++++++++------ 3 files changed, 177 insertions(+), 35 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 559c93c62a63c..25827946b57b8 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -69,6 +69,7 @@ type SubPoolsWithEra = BoundedBTreeMap, type RewardPoints = U256; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; +const U256_CUBED_ROOT: u128 = 48740834812604000000000000u128; /// Calculate the number of points to issue from a pool as `(current_points / current_balance) * /// new_funds` except for some zero edge cases; see logic and tests for details. @@ -127,7 +128,7 @@ pub struct Delegator { /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. /// Assuming no massive burning events, we expect this value to always be below total issuance. // TODO: ^ double check the above is an OK assumption - /// This value lines up with the `RewardPool.total_earnings` after a delegator claims a payout. + /// This value lines up with the `RewardPool::total_earnings` after a delegator claims a payout. reward_pool_total_earnings: BalanceOf, /// The era this delegator started unbonding at. unbonding_era: Option, @@ -409,7 +410,10 @@ pub mod pallet { pub fn join(origin: OriginFor, amount: BalanceOf, target: PoolId) -> DispatchResult { let who = ensure_signed(origin)?; // If a delegator already exists that means they already belong to a pool - ensure!(!DelegatorStorage::::contains_key(&who), Error::::AccountBelongsToOtherPool); + ensure!( + !DelegatorStorage::::contains_key(&who), + Error::::AccountBelongsToOtherPool + ); let mut bonded_pool = BondedPoolStorage::::get(target).ok_or(Error::::PoolNotFound)?; @@ -487,7 +491,8 @@ pub mod pallet { #[pallet::weight(666)] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let delegator = DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let delegator = + DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; let bonded_pool = BondedPoolStorage::::get(&delegator.pool).ok_or_else(|| { log!(error, "A bonded pool could not be found, this is a system logic error."); debug_assert!( @@ -507,7 +512,8 @@ pub mod pallet { #[pallet::weight(666)] pub fn unbond(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let delegator = DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let delegator = + DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; let mut bonded_pool = BondedPoolStorage::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; @@ -517,7 +523,8 @@ pub mod pallet { Self::do_reward_payout(who.clone(), delegator, &bonded_pool)?; // Re-fetch the delegator because they where updated by `do_reward_payout`. - let mut delegator = DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let mut delegator = + DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPoolsStorage::::get(delegator.pool).unwrap_or_default(); let current_era = T::StakingInterface::current_era(); @@ -568,7 +575,8 @@ pub mod pallet { #[pallet::weight(666)] pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let delegator = DelegatorStorage::::take(&who).ok_or(Error::::DelegatorNotFound)?; + let delegator = + DelegatorStorage::::take(&who).ok_or(Error::::DelegatorNotFound)?; let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); @@ -730,13 +738,12 @@ impl Pallet { // `bonded_pool.total_points`. In effect this allows each, single unit of balance (e.g. // plank) to be divvied up pro-rata among delegators based on points. // TODO this needs to be some sort of BigUInt arithmetic - let new_points = - T::BalanceToU256::convert(bonded_pool.points).saturating_mul(new_earnings); + let new_points = T::BalanceToU256::convert(bonded_pool.points).saturating_mul(new_earnings); // The points of the reward pool after taking into account the new earnings. Notice that // this only stays even or increases over time except for when we subtract delegator virtual // shares. - let current_points = reward_pool.points.saturating_add(new_points); // 39,800 + let current_points = reward_pool.points.saturating_add(new_points); // The rewards pool's earnings since the last time this delegator claimed a payout let new_earnings_since_last_claim = @@ -745,7 +752,10 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() || current_points.is_zero() { + let delegator_payout = if delegator_virtual_points.is_zero() || + current_points.is_zero() || + reward_pool.balance.is_zero() + { Zero::zero() } else { // Equivalent to `(delegator_virtual_points / current_points) * reward_pool.balance` @@ -820,7 +830,6 @@ impl Pallet { let pool_id = PoolIds::::get(pool_account)?; let mut sub_pools = SubPoolsStorage::::get(pool_id).unwrap_or_default(); - // TODO double check why we do slash_era + 1 let affected_range = (slash_era + 1)..=apply_era; // TODO Can have this as an input because we are inside of staking ledger @@ -892,6 +901,7 @@ impl PoolsInterface for Pallet { } // TODO +// - // - tests // - force pool creation // - rebond_rewards diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 43c719e40acea..3b4704efd6466 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -4,7 +4,7 @@ use frame_support::{assert_ok, parameter_types}; use frame_system::RawOrigin; pub type AccountId = u32; -pub type Balance = u32; +pub type Balance = u128; /// Pool 0's primary account id (i.e. its stash and controller account). pub const PRIMARY_ACCOUNT: u32 = 2536596763; @@ -200,3 +200,25 @@ impl ExtBuilder { }) } } + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn u256_to_balance_convert_works() { + assert_eq!(U256ToBalance::convert(0u32.into()), Zero::zero()); + assert_eq!(U256ToBalance::convert(Balance::max_value().into()), Balance::max_value()) + } + + #[test] + #[should_panic] + fn u256_to_balance_convert_panics_correctly() { + U256ToBalance::convert(U256::from(Balance::max_value()).saturating_add(1u32.into())); + } + + #[test] + fn balance_to_u256_convert_works() { + assert_eq!(BalanceToU256::convert(0u32.into()), U256::zero()); + assert_eq!(BalanceToU256::convert(Balance::max_value()), Balance::max_value().into()) + } +} diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 5842aa53d5e16..76ef5be8ee002 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -38,6 +38,15 @@ fn test_setup_works() { }) } +#[test] +fn u256_cubed_root_works() { + let cubed_root = U256::from(U256_CUBED_ROOT); + + let x = cubed_root.checked_mul(cubed_root).unwrap().checked_mul(cubed_root).unwrap(); + + assert_eq!(x, U256::max_value() - ); +} + mod points_to_issue { use super::*; #[test] @@ -92,7 +101,7 @@ mod balance_to_unbond { } } -mod primary_pool { +mod bonded_pool { use super::*; #[test] fn points_to_issue_works() {} @@ -327,18 +336,119 @@ mod claim_payout { #[test] fn calculate_delegator_payout_errors_if_a_delegator_is_unbonding() { ExtBuilder::default().build_and_execute(|| { - let primary_pool = BondedPoolStorage::::get(0).unwrap(); + let bonded_pool = BondedPoolStorage::::get(0).unwrap(); let reward_pool = RewardPoolStorage::::get(0).unwrap(); let mut delegator = DelegatorStorage::::get(10).unwrap(); delegator.unbonding_era = Some(0); assert_noop!( - Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator), + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator), Error::::AlreadyUnbonding ); }); } + #[test] + fn calculate_delegator_payout_saturates_at_u256() { + ExtBuilder::default().build_and_execute(|| { + let mut bonded_pool = BondedPoolStorage::::get(0).unwrap(); + let reward_pool = RewardPoolStorage::::get(0).unwrap(); + let mut delegator = DelegatorStorage::::get(10).unwrap(); + + // Given + bonded_pool.points = u128::MAX; + delegator.points = u128::MAX; + Balances::make_free_balance_be(&REWARDS_ACCOUNT, 1); + + // When + let (reward_pool, delegator, payout) = + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); + + // u128::MAX * u128::MAX * 1 / u128::MAX * u128::MAX + assert_eq!(payout, 1); + assert_eq!( + reward_pool, + RewardPool:: { + balance: 1, + points: u128::MAX.into(), + total_earnings: 1, + account_id: REWARDS_ACCOUNT, + } + ); + assert_eq!( + delegator, + Delegator:: { + pool: 0, + points: u128::MAX, + reward_pool_total_earnings: 1, + unbonding_era: None, + } + ); + + // Given that the reward pool has effectively earned 1 unit of balance + + // When + let (reward_pool, delegator, payout) = + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); + + // Then the overflowing arithmetic gives us bad results + assert_eq!(payout, 1); + assert_eq!( + reward_pool, + RewardPool:: { + balance: 1, + points: u128::MAX.into(), + total_earnings: 1, + account_id: REWARDS_ACCOUNT, + } + ); + assert_eq!( + delegator, + Delegator:: { + pool: 0, + points: u128::MAX, + reward_pool_total_earnings: 1, + unbonding_era: None, + } + ); + }); + + ExtBuilder::default().build_and_execute(|| { + let mut bonded_pool = BondedPoolStorage::::get(0).unwrap(); + let reward_pool = RewardPoolStorage::::get(0).unwrap(); + let mut delegator = DelegatorStorage::::get(10).unwrap(); + + bonded_pool.points = U256_CUBED_ROOT; + delegator.points = U256_CUBED_ROOT; + Balances::make_free_balance_be(&REWARDS_ACCOUNT, U256_CUBED_ROOT); + + let squared = U256::from(U256_CUBED_ROOT).saturating_mul(U256_CUBED_ROOT.into()); + + let (reward_pool, delegator, payout) = + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); + + assert_eq!(payout, U256_CUBED_ROOT); + assert_eq!( + reward_pool, + RewardPool:: { + balance: 1, + points: squared, + total_earnings: U256_CUBED_ROOT, + account_id: REWARDS_ACCOUNT, + } + ); + assert_eq!( + delegator, + Delegator:: { + pool: 0, + points: 2, + reward_pool_total_earnings: U256_CUBED_ROOT, + unbonding_era: None, + } + ); + }); + } + #[test] fn calculate_delegator_payout_works_with_a_pool_of_1() { let rew = |balance, points: u32, total_earnings| RewardPool:: { @@ -354,14 +464,14 @@ mod claim_payout { unbonding_era: None, }; ExtBuilder::default().build_and_execute(|| { - let primary_pool = BondedPoolStorage::::get(0).unwrap(); + let bonded_pool = BondedPoolStorage::::get(0).unwrap(); let reward_pool = RewardPoolStorage::::get(0).unwrap(); let delegator = DelegatorStorage::::get(10).unwrap(); // Given no rewards have been earned // When let (reward_pool, delegator, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); // Then assert_eq!(payout, 0); @@ -373,7 +483,7 @@ mod claim_payout { // When let (reward_pool, delegator, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); // Then assert_eq!(payout, 5); // (10 * 5 del virtual points / 10 * 5 pool points) * 5 pool balance @@ -385,7 +495,7 @@ mod claim_payout { // When let (reward_pool, delegator, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); // Then assert_eq!(payout, 10); // (10 * 10 del virtual points / 10 pool points) * 5 pool balance @@ -397,7 +507,7 @@ mod claim_payout { // When let (reward_pool, delegator, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, delegator).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); // Then assert_eq!(payout, 0); @@ -424,8 +534,8 @@ mod claim_payout { ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - let mut primary_pool = BondedPoolStorage::::get(0).unwrap(); - primary_pool.points = 100; + let mut bonded_pool = BondedPoolStorage::::get(0).unwrap(); + bonded_pool.points = 100; let reward_pool = RewardPoolStorage::::get(0).unwrap(); // Delegator with 10 points @@ -442,7 +552,7 @@ mod claim_payout { // When let (reward_pool, del_10, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_10).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_10).unwrap(); // Then assert_eq!(payout, 10); // (10 del virtual points / 100 pool points) * 100 pool balance @@ -453,7 +563,7 @@ mod claim_payout { // When let (reward_pool, del_40, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_40).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_40).unwrap(); // Then assert_eq!(payout, 40); // (400 del virtual points / 900 pool points) * 90 pool balance @@ -472,7 +582,7 @@ mod claim_payout { // When let (reward_pool, del_50, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_50).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_50).unwrap(); // Then assert_eq!(payout, 50); // (50 del virtual points / 50 pool points) * 50 pool balance @@ -486,7 +596,7 @@ mod claim_payout { // When let (reward_pool, del_10, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_10).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_10).unwrap(); // Then assert_eq!(payout, 5); // (500 del virtual points / 5,000 pool points) * 50 pool balance @@ -497,7 +607,7 @@ mod claim_payout { // When let (reward_pool, del_40, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_40).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_40).unwrap(); // Then assert_eq!(payout, 20); // (2,000 del virtual points / 4,500 pool points) * 45 pool balance @@ -511,7 +621,7 @@ mod claim_payout { // When let (reward_pool, del_50, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_50).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_50).unwrap(); // Then assert_eq!(payout, 50); // (5,000 del virtual points / 7,5000 pool points) * 75 pool balance @@ -522,7 +632,7 @@ mod claim_payout { 25, // old pool points + points from new earnings - del points. // - // points from new earnings = new earnings(50) * primary_pool.points(100) + // points from new earnings = new earnings(50) * bonded_pool.points(100) // del points = delegator.points(50) * new_earnings_since_last_claim (100) (2_500 + 50 * 100) - 50 * 100, 200, @@ -533,7 +643,7 @@ mod claim_payout { // When let (reward_pool, del_10, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_10).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_10).unwrap(); // Then assert_eq!(payout, 5); @@ -548,7 +658,7 @@ mod claim_payout { // When let (reward_pool, del_10, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_10).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_10).unwrap(); // Then assert_eq!(payout, 40); @@ -559,7 +669,7 @@ mod claim_payout { 380, // old pool points + points from new earnings - del points // - // points from new earnings = new earnings(400) * primary_pool.points(100) + // points from new earnings = new earnings(400) * bonded_pool.points(100) // del points = delegator.points(10) * new_earnings_since_last_claim(400) (2_000 + 400 * 100) - 10 * 400, 600 @@ -574,7 +684,7 @@ mod claim_payout { // When let (reward_pool, del_10, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_10).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_10).unwrap(); // Then assert_eq!(payout, 2); // (200 del virtual points / 38,000 pool points) * 400 pool balance @@ -585,7 +695,7 @@ mod claim_payout { // When let (reward_pool, del_40, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_40).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_40).unwrap(); // Then assert_eq!(payout, 188); // (18,800 del virtual points / 39,800 pool points) * 399 pool balance @@ -596,7 +706,7 @@ mod claim_payout { // When let (reward_pool, del_50, payout) = - Pools::calculate_delegator_payout(&primary_pool, reward_pool, del_50).unwrap(); + Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_50).unwrap(); // Then assert_eq!(payout, 210); // (21,000 / 21,000) * 210 From c58b58dc56f46818a731e4133394ee14af1d0377 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 24 Jan 2022 15:31:58 -0800 Subject: [PATCH 029/299] Test: do_reward_payout_works + some comments and removing bad tests --- frame/pools/src/lib.rs | 21 ++- frame/pools/src/tests.rs | 389 +++++++++++++++++++++++++++------------ 2 files changed, 291 insertions(+), 119 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 25827946b57b8..a3c65876e5344 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -69,7 +69,6 @@ type SubPoolsWithEra = BoundedBTreeMap, type RewardPoints = U256; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; -const U256_CUBED_ROOT: u128 = 48740834812604000000000000u128; /// Calculate the number of points to issue from a pool as `(current_points / current_balance) * /// new_funds` except for some zero edge cases; see logic and tests for details. @@ -117,7 +116,7 @@ fn balance_to_unbond( } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] -#[cfg_attr(feature = "std", derive(Clone, PartialEq))] +#[cfg_attr(feature = "std", derive(Clone, PartialEq, DefaultNoBound))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct Delegator { @@ -128,7 +127,8 @@ pub struct Delegator { /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. /// Assuming no massive burning events, we expect this value to always be below total issuance. // TODO: ^ double check the above is an OK assumption - /// This value lines up with the `RewardPool::total_earnings` after a delegator claims a payout. + /// This value lines up with the `RewardPool::total_earnings` after a delegator claims a + /// payout. reward_pool_total_earnings: BalanceOf, /// The era this delegator started unbonding at. unbonding_era: Option, @@ -737,7 +737,6 @@ impl Pallet { // been earned by the reward pool, we inflate the reward pool points by // `bonded_pool.total_points`. In effect this allows each, single unit of balance (e.g. // plank) to be divvied up pro-rata among delegators based on points. - // TODO this needs to be some sort of BigUInt arithmetic let new_points = T::BalanceToU256::convert(bonded_pool.points).saturating_mul(new_earnings); // The points of the reward pool after taking into account the new earnings. Notice that @@ -795,7 +794,6 @@ impl Pallet { ) -> DispatchResult { let reward_pool = RewardPoolStorage::::get(&delegator.pool).ok_or_else(|| { log!(error, "A reward pool could not be found, this is a system logic error."); - debug_assert!(false, "A reward pool could not be found, this is a system logic error."); Error::::RewardPoolNotFound })?; @@ -817,6 +815,19 @@ impl Pallet { Ok(()) } + //The current approach here is to share `BTreeMap>` with the staking + // API. This is arguably a leaky, suboptimal API because both sides have to share this + // non-trivial data structure. With the current design we do this because we track the unbonding + // balance in both the pallet-staking `unlocking` chunks and in here with the pallet-pools + // `SubPools`. Because both pallets need to know about slashes to unbonding funds we either have + // to replicate the slashing logic between the pallets, or share some data. A ALTERNATIVE is + // having the pallet-pools read the unbonding balance per era directly from pallet-staking. The + // downside of this is that once a delegator calls `withdraw_unbonded`, the chunk is removed and + // we can't keep track of the balance for that `UnbondPool` anymore, thus we must merge the + // balance and points of that `UnbondPool` with the `no_era` pool immediately upon calling + // withdraw_unbonded. We choose not to do this because if there was a slash, it would negatively + // affect the points:balance ratio of the `no_era` pool for everyone, including those who may + // not have been unbonding in eras effected by the slash. fn do_slash( // This would be the nominator account pool_account: &T::AccountId, diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 76ef5be8ee002..23c9e7b9e7a00 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -38,15 +38,6 @@ fn test_setup_works() { }) } -#[test] -fn u256_cubed_root_works() { - let cubed_root = U256::from(U256_CUBED_ROOT); - - let x = cubed_root.checked_mul(cubed_root).unwrap().checked_mul(cubed_root).unwrap(); - - assert_eq!(x, U256::max_value() - ); -} - mod points_to_issue { use super::*; #[test] @@ -348,107 +339,6 @@ mod claim_payout { }); } - #[test] - fn calculate_delegator_payout_saturates_at_u256() { - ExtBuilder::default().build_and_execute(|| { - let mut bonded_pool = BondedPoolStorage::::get(0).unwrap(); - let reward_pool = RewardPoolStorage::::get(0).unwrap(); - let mut delegator = DelegatorStorage::::get(10).unwrap(); - - // Given - bonded_pool.points = u128::MAX; - delegator.points = u128::MAX; - Balances::make_free_balance_be(&REWARDS_ACCOUNT, 1); - - // When - let (reward_pool, delegator, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); - - // u128::MAX * u128::MAX * 1 / u128::MAX * u128::MAX - assert_eq!(payout, 1); - assert_eq!( - reward_pool, - RewardPool:: { - balance: 1, - points: u128::MAX.into(), - total_earnings: 1, - account_id: REWARDS_ACCOUNT, - } - ); - assert_eq!( - delegator, - Delegator:: { - pool: 0, - points: u128::MAX, - reward_pool_total_earnings: 1, - unbonding_era: None, - } - ); - - // Given that the reward pool has effectively earned 1 unit of balance - - // When - let (reward_pool, delegator, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); - - // Then the overflowing arithmetic gives us bad results - assert_eq!(payout, 1); - assert_eq!( - reward_pool, - RewardPool:: { - balance: 1, - points: u128::MAX.into(), - total_earnings: 1, - account_id: REWARDS_ACCOUNT, - } - ); - assert_eq!( - delegator, - Delegator:: { - pool: 0, - points: u128::MAX, - reward_pool_total_earnings: 1, - unbonding_era: None, - } - ); - }); - - ExtBuilder::default().build_and_execute(|| { - let mut bonded_pool = BondedPoolStorage::::get(0).unwrap(); - let reward_pool = RewardPoolStorage::::get(0).unwrap(); - let mut delegator = DelegatorStorage::::get(10).unwrap(); - - bonded_pool.points = U256_CUBED_ROOT; - delegator.points = U256_CUBED_ROOT; - Balances::make_free_balance_be(&REWARDS_ACCOUNT, U256_CUBED_ROOT); - - let squared = U256::from(U256_CUBED_ROOT).saturating_mul(U256_CUBED_ROOT.into()); - - let (reward_pool, delegator, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); - - assert_eq!(payout, U256_CUBED_ROOT); - assert_eq!( - reward_pool, - RewardPool:: { - balance: 1, - points: squared, - total_earnings: U256_CUBED_ROOT, - account_id: REWARDS_ACCOUNT, - } - ); - assert_eq!( - delegator, - Delegator:: { - pool: 0, - points: 2, - reward_pool_total_earnings: U256_CUBED_ROOT, - unbonding_era: None, - } - ); - }); - } - #[test] fn calculate_delegator_payout_works_with_a_pool_of_1() { let rew = |balance, points: u32, total_earnings| RewardPool:: { @@ -534,9 +424,7 @@ mod claim_payout { ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - let mut bonded_pool = BondedPoolStorage::::get(0).unwrap(); - bonded_pool.points = 100; - + let bonded_pool = BondedPoolStorage::::get(0).unwrap(); let reward_pool = RewardPoolStorage::::get(0).unwrap(); // Delegator with 10 points let del_10 = DelegatorStorage::::get(10).unwrap(); @@ -547,6 +435,7 @@ mod claim_payout { // Given we have a total of 100 points split among the delegators assert_eq!(del_50.points + del_40.points + del_10.points, 100); + assert_eq!(bonded_pool.points, 100); // and the reward pool has earned 100 in rewards Balances::make_free_balance_be(&REWARDS_ACCOUNT, 100); @@ -714,9 +603,281 @@ mod claim_payout { assert_eq!(reward_pool, rew(0, 21_000 - 50 * 420, 620)); }); } + + #[test] + fn do_reward_payout_works() { + let rew = |balance, points: u32, total_earnings| RewardPool:: { + balance, + points: points.into(), + total_earnings, + account_id: REWARDS_ACCOUNT, + }; + let del = |points, reward_pool_total_earnings| Delegator:: { + pool: 0, + points, + reward_pool_total_earnings, + unbonding_era: None, + }; + + ExtBuilder::default() + .add_delegators(vec![(40, 40), (50, 50)]) + .build_and_execute(|| { + let bonded_pool = BondedPoolStorage::::get(0).unwrap(); + + // Given the bonded pool has 100 points + assert_eq!(bonded_pool.points, 100); + // Each delegator currently has a free balance of - + Balances::make_free_balance_be(&10, 0); + Balances::make_free_balance_be(&40, 0); + Balances::make_free_balance_be(&50, 0); + // and the reward pool has earned 100 in rewards + Balances::make_free_balance_be(&REWARDS_ACCOUNT, 100); + + // When + assert_ok!(Pools::do_reward_payout( + 10, + DelegatorStorage::get(10).unwrap(), + &bonded_pool + )); + + // Then + // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool + // balance + assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 100)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(90, 100 * 100 - 100 * 10, 100) + ); + assert_eq!(Balances::free_balance(&10), 10); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 90); + + // When + assert_ok!(Pools::do_reward_payout( + 40, + DelegatorStorage::get(40).unwrap(), + &bonded_pool + )); + + // Then + // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance + assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 100)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(50, 9_000 - 100 * 40, 100) + ); + assert_eq!(Balances::free_balance(&40), 40); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 50); + + // When + assert_ok!(Pools::do_reward_payout( + 50, + DelegatorStorage::get(50).unwrap(), + &bonded_pool + )); + + // Then + // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance + assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 100)); + assert_eq!(RewardPoolStorage::::get(0).unwrap(), rew(0, 0, 100)); + assert_eq!(Balances::free_balance(&50), 50); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); + + // Given the reward pool has some new rewards + Balances::make_free_balance_be(&REWARDS_ACCOUNT, 50); + + // When + assert_ok!(Pools::do_reward_payout( + 10, + DelegatorStorage::get(10).unwrap(), + &bonded_pool + )); + + // Then + // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance + assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 150)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(45, 5_000 - 50 * 10, 150) + ); + assert_eq!(Balances::free_balance(&10), 10 + 5); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 45); + + // When + assert_ok!(Pools::do_reward_payout( + 40, + DelegatorStorage::get(40).unwrap(), + &bonded_pool + )); + + // Then + // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool + // balance + assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 150)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(25, 4_500 - 50 * 40, 150) + ); + assert_eq!(Balances::free_balance(&40), 40 + 20); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 25); + + // Given del 50 hasn't claimed and the reward pools has just earned 50 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 50)); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 75); + + // When + assert_ok!(Pools::do_reward_payout( + 50, + DelegatorStorage::get(50).unwrap(), + &bonded_pool + )); + + // Then + // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 + // pool balance + assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 200)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew( + 25, + // old pool points + points from new earnings - del points. + // + // points from new earnings = new earnings(50) * bonded_pool.points(100) + // del points = delegator.points(50) * new_earnings_since_last_claim (100) + (2_500 + 50 * 100) - 50 * 100, + 200, + ) + ); + assert_eq!(Balances::free_balance(&50), 50 + 50); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 25); + + // When + assert_ok!(Pools::do_reward_payout( + 10, + DelegatorStorage::get(10).unwrap(), + &bonded_pool + )); + + // Then + // We expect a payout of 5 + assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 200)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(20, 2_500 - 10 * 50, 200) + ); + assert_eq!(Balances::free_balance(&10), 15 + 5); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 20); + + // Given del 40 hasn't claimed and the reward pool has just earned 400 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 400)); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 420); + + // When + assert_ok!(Pools::do_reward_payout( + 10, + DelegatorStorage::get(10).unwrap(), + &bonded_pool + )); + + // Then + // We expect a payout of 40 + assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 600)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew( + 380, + // old pool points + points from new earnings - del points + // + // points from new earnings = new earnings(400) * bonded_pool.points(100) + // del points = delegator.points(10) * new_earnings_since_last_claim(400) + (2_000 + 400 * 100) - 10 * 400, + 600 + ) + ); + assert_eq!(Balances::free_balance(&10), 20 + 40); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 380); + + // Given del 40 + del 50 haven't claimed and the reward pool has earned 20 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 20)); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 400); + + // When + assert_ok!(Pools::do_reward_payout( + 10, + DelegatorStorage::get(10).unwrap(), + &bonded_pool + )); + + // Then + // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool + // balance + assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 620)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(398, (38_000 + 20 * 100) - 10 * 20, 620) + ); + assert_eq!(Balances::free_balance(&10), 60 + 2); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 398); + + // When + assert_ok!(Pools::do_reward_payout( + 40, + DelegatorStorage::get(40).unwrap(), + &bonded_pool + )); + + // Then + // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 + // pool balance + assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 620)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(210, 39_800 - 40 * 470, 620) + ); + assert_eq!(Balances::free_balance(&40), 60 + 188); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 210); + + // When + assert_ok!(Pools::do_reward_payout( + 50, + DelegatorStorage::get(50).unwrap(), + &bonded_pool + )); + + // Then + // Expect payout of 210: (21,000 / 21,000) * 210 + assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 620)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(0, 21_000 - 50 * 420, 620) + ); + assert_eq!(Balances::free_balance(&50), 100 + 210); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); + }); + } } -mod unbond {} +mod unbond { + use super::*; + + #[test] + fn unbond_errors_correctly() { + ExtBuilder::default().build_and_execute(|| { + assert_noop!(Pools::unbond(Origin::signed(11)), Error::::DelegatorNotFound); + + // Add the delegator + let delegator = Delegator { pool: 1, points: 10, ..Default::default() }; + DelegatorStorage::::insert(11, delegator); + + assert_noop!(Pools::unbond(Origin::signed(11)), Error::::PoolNotFound); + + // Add bonded pool to go along with the delegator + let bonded_pool = BondedPool { account_id: 101, points: 10 }; + BondedPoolStorage::::insert(1, bonded_pool); + + assert_noop!(Pools::unbond(Origin::signed(11)), Error::::RewardPoolNotFound); + }); + } +} mod withdraw_unbonded {} From 1797351f0238f424d6416ac55da506b71b885792 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 24 Jan 2022 16:57:19 -0800 Subject: [PATCH 030/299] Test: claim_payout_works --- frame/pools/src/lib.rs | 4 - frame/pools/src/tests.rs | 254 ++++++++++++++++++++++++++++++++++----- 2 files changed, 222 insertions(+), 36 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index a3c65876e5344..400dd0c326880 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -495,10 +495,6 @@ pub mod pallet { DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; let bonded_pool = BondedPoolStorage::::get(&delegator.pool).ok_or_else(|| { log!(error, "A bonded pool could not be found, this is a system logic error."); - debug_assert!( - false, - "A bonded pool could not be found, this is a system logic error." - ); Error::::PoolNotFound })?; diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 23c9e7b9e7a00..a037fa7dbbb1c 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -324,6 +324,204 @@ mod join { mod claim_payout { use super::*; + fn del(points: Balance, reward_pool_total_earnings: Balance) -> Delegator { + Delegator { pool: 0, points, reward_pool_total_earnings, unbonding_era: None } + } + + fn rew(balance: Balance, points: u32, total_earnings: Balance) -> RewardPool { + RewardPool { balance, points: points.into(), total_earnings, account_id: REWARDS_ACCOUNT } + } + + #[test] + fn claim_payout_works() { + ExtBuilder::default() + .add_delegators(vec![(40, 40), (50, 50)]) + .build_and_execute(|| { + // Given each delegator currently has a free balance of + Balances::make_free_balance_be(&10, 0); + Balances::make_free_balance_be(&40, 0); + Balances::make_free_balance_be(&50, 0); + // and the reward pool has earned 100 in rewards + Balances::make_free_balance_be(&REWARDS_ACCOUNT, 100); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(10))); + + // Then + // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool + // balance + assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 100)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(90, 100 * 100 - 100 * 10, 100) + ); + assert_eq!(Balances::free_balance(&10), 10); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 90); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(40))); + + // Then + // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance + assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 100)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(50, 9_000 - 100 * 40, 100) + ); + assert_eq!(Balances::free_balance(&40), 40); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 50); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(50))); + + // Then + // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance + assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 100)); + assert_eq!(RewardPoolStorage::::get(0).unwrap(), rew(0, 0, 100)); + assert_eq!(Balances::free_balance(&50), 50); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); + + // Given the reward pool has some new rewards + Balances::make_free_balance_be(&REWARDS_ACCOUNT, 50); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(10))); + + // Then + // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance + assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 150)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(45, 5_000 - 50 * 10, 150) + ); + assert_eq!(Balances::free_balance(&10), 10 + 5); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 45); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(40))); + + // Then + // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool + // balance + assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 150)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(25, 4_500 - 50 * 40, 150) + ); + assert_eq!(Balances::free_balance(&40), 40 + 20); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 25); + + // Given del 50 hasn't claimed and the reward pools has just earned 50 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 50)); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 75); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(50))); + + // Then + // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 + // pool balance + assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 200)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew( + 25, + // old pool points + points from new earnings - del points. + // + // points from new earnings = new earnings(50) * bonded_pool.points(100) + // del points = delegator.points(50) * new_earnings_since_last_claim (100) + (2_500 + 50 * 100) - 50 * 100, + 200, + ) + ); + assert_eq!(Balances::free_balance(&50), 50 + 50); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 25); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(10))); + + // Then + // We expect a payout of 5 + assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 200)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(20, 2_500 - 10 * 50, 200) + ); + assert_eq!(Balances::free_balance(&10), 15 + 5); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 20); + + // Given del 40 hasn't claimed and the reward pool has just earned 400 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 400)); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 420); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(10))); + + // Then + // We expect a payout of 40 + assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 600)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew( + 380, + // old pool points + points from new earnings - del points + // + // points from new earnings = new earnings(400) * bonded_pool.points(100) + // del points = delegator.points(10) * new_earnings_since_last_claim(400) + (2_000 + 400 * 100) - 10 * 400, + 600 + ) + ); + assert_eq!(Balances::free_balance(&10), 20 + 40); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 380); + + // Given del 40 + del 50 haven't claimed and the reward pool has earned 20 + assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 20)); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 400); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(10))); + + // Then + // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool + // balance + assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 620)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(398, (38_000 + 20 * 100) - 10 * 20, 620) + ); + assert_eq!(Balances::free_balance(&10), 60 + 2); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 398); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(40))); + + // Then + // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 + // pool balance + assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 620)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(210, 39_800 - 40 * 470, 620) + ); + assert_eq!(Balances::free_balance(&40), 60 + 188); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 210); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(50))); + + // Then + // Expect payout of 210: (21,000 / 21,000) * 210 + assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 620)); + assert_eq!( + RewardPoolStorage::::get(0).unwrap(), + rew(0, 21_000 - 50 * 420, 620) + ); + assert_eq!(Balances::free_balance(&50), 100 + 210); + assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); + }); + } + #[test] fn calculate_delegator_payout_errors_if_a_delegator_is_unbonding() { ExtBuilder::default().build_and_execute(|| { @@ -341,12 +539,6 @@ mod claim_payout { #[test] fn calculate_delegator_payout_works_with_a_pool_of_1() { - let rew = |balance, points: u32, total_earnings| RewardPool:: { - balance, - points: points.into(), - total_earnings, - account_id: REWARDS_ACCOUNT, - }; let del = |reward_pool_total_earnings| Delegator:: { pool: 0, points: 10, @@ -408,19 +600,6 @@ mod claim_payout { #[test] fn calculate_delegator_payout_works_with_a_pool_of_3() { - let rew = |balance, points: u32, total_earnings| RewardPool:: { - balance, - points: points.into(), - total_earnings, - account_id: REWARDS_ACCOUNT, - }; - let del = |points, reward_pool_total_earnings| Delegator:: { - pool: 0, - points, - reward_pool_total_earnings, - unbonding_era: None, - }; - ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { @@ -606,19 +785,6 @@ mod claim_payout { #[test] fn do_reward_payout_works() { - let rew = |balance, points: u32, total_earnings| RewardPool:: { - balance, - points: points.into(), - total_earnings, - account_id: REWARDS_ACCOUNT, - }; - let del = |points, reward_pool_total_earnings| Delegator:: { - pool: 0, - points, - reward_pool_total_earnings, - unbonding_era: None, - }; - ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { @@ -854,6 +1020,30 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); }); } + + #[test] + fn do_reward_payout_errors_correctly() { + ExtBuilder::default().build_and_execute(|| { + let bonded_pool = BondedPoolStorage::::get(0).unwrap(); + let mut delegator = DelegatorStorage::::get(10).unwrap(); + + // The only place this can return an error is with the balance transfer from the + // reward account to the delegator, and as far as this comment author can tell this + // can only if storage is in a bad state prior to `do_reward_payout` being called. + + // Given + delegator.points = 15; + assert_eq!(bonded_pool.points, 10); + Balances::make_free_balance_be(&REWARDS_ACCOUNT, 10); + + // Then + // Expect attempt payout of 15/10 * 10 when free balance is actually 10 + assert_noop!( + Pools::do_reward_payout(10, delegator, &bonded_pool), + pallet_balances::Error::::InsufficientBalance + ); + }); + } } mod unbond { From a022831760aa43ed4b32ee915cd27b636ca5b0e8 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 25 Jan 2022 10:13:50 -0800 Subject: [PATCH 031/299] Test: unbond_pool_of_3_works --- frame/pools/src/mock.rs | 2 +- frame/pools/src/tests.rs | 85 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 3b4704efd6466..659d7bc0ae63f 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -181,8 +181,8 @@ impl ExtBuilder { // make a pool let amount_to_bond = ::StakingInterface::minimum_bond(); Balances::make_free_balance_be(&10, amount_to_bond * 2); - assert_ok!(Pools::create(RawOrigin::Signed(10).into(), 0, vec![100], amount_to_bond)); + for (account_id, bonded) in self.delegators { Balances::make_free_balance_be(&account_id, bonded * 2); diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index a037fa7dbbb1c..5155a78d07486 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -5,6 +5,20 @@ use crate::mock::{ }; use frame_support::{assert_noop, assert_ok}; +// https://stackoverflow.com/questions/27582739/how-do-i-create-a-hashmap-literal +macro_rules! collection { + // map-like + ($($k:expr => $v:expr),* $(,)?) => {{ + use std::iter::{Iterator, IntoIterator}; + Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*])) + }}; + // set-like + ($($v:expr),* $(,)?) => {{ + use std::iter::{Iterator, IntoIterator}; + Iterator::collect(IntoIterator::into_iter([$($v,)*])) + }}; +} + #[test] fn test_setup_works() { ExtBuilder::default().build_and_execute(|| { @@ -1049,6 +1063,77 @@ mod claim_payout { mod unbond { use super::*; + #[test] + fn unbond_pool_of_1_works() { + ExtBuilder::default().build_and_execute(|| { + assert_ok!(Pools::unbond(Origin::signed(10))); + + assert_eq!( + SubPoolsStorage::::get(0).unwrap().with_era.into_inner(), + collection! { 0 => UnbondPool { points: 10, balance: 10 }} + ); + + assert_eq!( + BondedPoolStorage::::get(0).unwrap(), + BondedPool { account_id: PRIMARY_ACCOUNT, points: 0 } + ); + + assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 0); + }); + } + + #[test] + fn unbond_pool_of_3_works() { + ExtBuilder::default() + .add_delegators(vec![(40, 40), (550, 550)]) + .build_and_execute(|| { + // Given a slash from 600 -> 100 + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); + + // When + assert_ok!(Pools::unbond(Origin::signed(40))); + + // Then + assert_eq!( + SubPoolsStorage::::get(0).unwrap().with_era.into_inner(), + collection! { 0 => UnbondPool { points: 6, balance: 6 }} + ); + assert_eq!( + BondedPoolStorage::::get(0).unwrap(), + BondedPool { account_id: PRIMARY_ACCOUNT, points: 560 } + ); + assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 94); + + // When + assert_ok!(Pools::unbond(Origin::signed(10))); + + // Then + assert_eq!( + SubPoolsStorage::::get(0).unwrap().with_era.into_inner(), + collection! { 0 => UnbondPool { points: 7, balance: 7 }} + ); + assert_eq!( + BondedPoolStorage::::get(0).unwrap(), + BondedPool { account_id: PRIMARY_ACCOUNT, points: 550 } + ); + assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 93); + + // When + assert_ok!(Pools::unbond(Origin::signed(550))); + + // Then + assert_eq!( + SubPoolsStorage::::get(0).unwrap().with_era.into_inner(), + collection! { 0 => UnbondPool { points: 100, balance: 100 }} + ); + assert_eq!( + BondedPoolStorage::::get(0).unwrap(), + BondedPool { account_id: PRIMARY_ACCOUNT, points: 0 } + ); + assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 0); + }); + } + #[test] fn unbond_errors_correctly() { ExtBuilder::default().build_and_execute(|| { From b5626263a4561731ee45b9f794d205b334e438c3 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 25 Jan 2022 11:52:05 -0800 Subject: [PATCH 032/299] Test: withdraw + add active_bonded param to do_slash --- frame/pools/src/lib.rs | 86 ++++++++++----------- frame/pools/src/mock.rs | 2 +- frame/pools/src/tests.rs | 136 ++++++++++++++++++++++++++++------ frame/staking/src/lib.rs | 2 +- primitives/staking/src/lib.rs | 17 +++++ 5 files changed, 173 insertions(+), 70 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 400dd0c326880..329a1f928fbc9 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -34,7 +34,7 @@ use frame_support::{ pallet_prelude::*, storage::bounded_btree_map::BoundedBTreeMap, traits::{Currency, ExistenceRequirement, Get}, - DefaultNoBound, RuntimeDebugNoBound, + transactional, DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_core::U256; @@ -78,8 +78,9 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), + (true, true) | (false, true) => { + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) + }, (true, false) => { // The pool was totally slashed. @@ -105,7 +106,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero() + return Zero::zero(); } // Equivalent of (current_balance / current_points) * delegator_points @@ -176,8 +177,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) < - BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) + < BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -266,7 +267,7 @@ impl SubPools { // For the first `0..MaxUnbonding` eras of the chain we don't need to do anything. // I.E. if `MaxUnbonding` is 5 and we are in era 4 we can add a pool for this era and // have exactly `MaxUnbonding` pools. - return self + return self; } // I.E. if `MaxUnbonding` is 5 and current era is 10, we only want to retain pools 6..=10. @@ -371,13 +372,15 @@ pub mod pallet { #[pallet::error] #[cfg_attr(test, derive(PartialEq))] pub enum Error { - /// The given (bonded) pool id does not exist. + /// A (bonded) pool id does not exist. PoolNotFound, - /// The given account is not a delegator. + /// An account is not a delegator. DelegatorNotFound, - // The given reward pool does not exist. In all cases this is a system logic error. + /// A reward pool does not exist. In all cases this is a system logic error. RewardPoolNotFound, - /// The account is already delegating in another pool. An account may only belong to one + /// A sub pool does not exist. + SubPoolsNotFound, + /// An account is already delegating in another pool. An account may only belong to one /// pool at a time. AccountBelongsToOtherPool, /// The pool has insufficient balance to bond as a nominator. @@ -391,7 +394,7 @@ pub mod pallet { /// The given pool id cannot be used to create a new pool because it is already in use. IdInUse, /// The amount does not meet the minimum bond to start nominating. - MinimiumBondNotMet, + MinimumBondNotMet, /// The transaction could not be executed due to overflow risk for the pool. OverflowRisk, /// An error from the staking pallet. @@ -423,7 +426,7 @@ pub mod pallet { // the active balance is slashed below the minimum bonded or the account cannot be // found, we exit early. if !T::StakingInterface::can_bond_extra(&bonded_pool.account_id, amount) { - return Err(Error::::StakingError.into()) + return Err(Error::::StakingError.into()); } // We don't actually care about writing the reward pool, we just need its @@ -565,24 +568,22 @@ pub mod pallet { Ok(()) } - // TODO the first person that withdraws for a pool withdraws there funds and then remaining - // funds and points are merged into the `no_era` pool - then for unbond pools we can just - // read balance from the unlocking chunks in staking #[pallet::weight(666)] pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let delegator = - DelegatorStorage::::take(&who).ok_or(Error::::DelegatorNotFound)?; + DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); if current_era.saturating_sub(unbonding_era) < T::StakingInterface::bonding_duration() { - return Err(Error::::NotUnbondedYet.into()) + return Err(Error::::NotUnbondedYet.into()); }; - let mut sub_pools = SubPoolsStorage::::get(delegator.pool).unwrap_or_default(); + let mut sub_pools = + SubPoolsStorage::::get(delegator.pool).ok_or(Error::::SubPoolsNotFound)?; - let balance_to_unbond = if let Some(pool) = sub_pools.with_era.get_mut(¤t_era) { + let balance_to_unbond = if let Some(pool) = sub_pools.with_era.get_mut(&unbonding_era) { let balance_to_unbond = pool.balance_to_unbond(delegator.points); pool.points = pool.points.saturating_sub(delegator.points); pool.balance = pool.balance.saturating_sub(balance_to_unbond); @@ -591,7 +592,6 @@ pub mod pallet { } else { // A pool does not belong to this era, so it must have been merged to the era-less // pool. - // trailing_pool???? let balance_to_unbond = sub_pools.no_era.balance_to_unbond(delegator.points); sub_pools.no_era.points = sub_pools.no_era.points.saturating_sub(delegator.points); sub_pools.no_era.balance = @@ -600,6 +600,8 @@ pub mod pallet { balance_to_unbond }; + println!("balance_to_unbond {:?}", balance_to_unbond); + let bonded_pool = BondedPoolStorage::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; T::Currency::transfer( @@ -610,6 +612,7 @@ pub mod pallet { )?; SubPoolsStorage::::insert(delegator.pool, sub_pools); + DelegatorStorage::::remove(&who); Self::deposit_event(Event::::Withdrawn { delegator: who, @@ -620,7 +623,10 @@ pub mod pallet { Ok(()) } + // NOTE: This is transactional because we cannot totally predict if the underlying bond will + // work until we transfer the funds. #[pallet::weight(666)] + #[transactional] pub fn create( origin: OriginFor, id: PoolId, @@ -630,7 +636,7 @@ pub mod pallet { let who = ensure_signed(origin)?; ensure!(!BondedPoolStorage::::contains_key(id), Error::::IdInUse); - ensure!(amount >= T::StakingInterface::minimum_bond(), Error::::MinimiumBondNotMet); + ensure!(amount >= T::StakingInterface::minimum_bond(), Error::::MinimumBondNotMet); // TODO create can_* fns so we can bail in the beggining if some pre-conditions are not // met T::StakingInterface::can_bond() // T::StakingInterface::can_nominate() @@ -747,9 +753,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() || - current_points.is_zero() || - reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() + || current_points.is_zero() + || reward_pool.balance.is_zero() { Zero::zero() } else { @@ -811,19 +817,6 @@ impl Pallet { Ok(()) } - //The current approach here is to share `BTreeMap>` with the staking - // API. This is arguably a leaky, suboptimal API because both sides have to share this - // non-trivial data structure. With the current design we do this because we track the unbonding - // balance in both the pallet-staking `unlocking` chunks and in here with the pallet-pools - // `SubPools`. Because both pallets need to know about slashes to unbonding funds we either have - // to replicate the slashing logic between the pallets, or share some data. A ALTERNATIVE is - // having the pallet-pools read the unbonding balance per era directly from pallet-staking. The - // downside of this is that once a delegator calls `withdraw_unbonded`, the chunk is removed and - // we can't keep track of the balance for that `UnbondPool` anymore, thus we must merge the - // balance and points of that `UnbondPool` with the `no_era` pool immediately upon calling - // withdraw_unbonded. We choose not to do this because if there was a slash, it would negatively - // affect the points:balance ratio of the `no_era` pool for everyone, including those who may - // not have been unbonding in eras effected by the slash. fn do_slash( // This would be the nominator account pool_account: &T::AccountId, @@ -833,15 +826,14 @@ impl Pallet { slash_era: EraIndex, // Era the slash is applied in apply_era: EraIndex, + // The current active bonded of the account (i.e. `StakingLedger::active`) + active_bonded_balance: BalanceOf, ) -> Option<(BalanceOf, BTreeMap>)> { let pool_id = PoolIds::::get(pool_account)?; let mut sub_pools = SubPoolsStorage::::get(pool_id).unwrap_or_default(); let affected_range = (slash_era + 1)..=apply_era; - // TODO Can have this as an input because we are inside of staking ledger - let bonded_balance = T::StakingInterface::bonded_balance(pool_account); - // Note that this doesn't count the balance in the `no_era` pool let unbonding_affected_balance: BalanceOf = affected_range.clone().fold(BalanceOf::::zero(), |balance_sum, era| { @@ -851,10 +843,11 @@ impl Pallet { balance_sum } }); - let total_affected_balance = bonded_balance.saturating_add(unbonding_affected_balance); + let total_affected_balance = + active_bonded_balance.saturating_add(unbonding_affected_balance); if total_affected_balance.is_zero() { - return Some((Zero::zero(), Default::default())) + return Some((Zero::zero(), Default::default())); } if slash_amount > total_affected_balance { // TODO this shouldn't happen as long as MaxBonding pools is greater thant the slash @@ -883,9 +876,9 @@ impl Pallet { SubPoolsStorage::::insert(pool_id, sub_pools); - // Equivalent to `(slash_amount / total_affected_balance) * bonded_balance` + // Equivalent to `(slash_amount / total_affected_balance) * active_bonded_balance` let slashed_bonded_pool_balance = slash_amount - .saturating_mul(bonded_balance) + .saturating_mul(active_bonded_balance) // We check for zero above .div(total_affected_balance); @@ -902,8 +895,9 @@ impl PoolsInterface for Pallet { slash_amount: Self::Balance, slash_era: EraIndex, apply_era: EraIndex, + active_bonded: BalanceOf, ) -> Option<(Self::Balance, BTreeMap)> { - Self::do_slash(pool_account, slash_amount, slash_era, apply_era) + Self::do_slash(pool_account, slash_amount, slash_era, apply_era, active_bonded) } } diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 659d7bc0ae63f..44161fae2d39e 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -12,7 +12,7 @@ pub const PRIMARY_ACCOUNT: u32 = 2536596763; pub const REWARDS_ACCOUNT: u32 = 736857005; parameter_types! { - static CurrentEra: EraIndex = 0; + pub static CurrentEra: EraIndex = 0; static BondedBalanceMap: std::collections::HashMap = Default::default(); pub static CanBondExtra: bool = true; } diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 5155a78d07486..7eef18913b3b7 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1,22 +1,23 @@ use super::*; use crate::mock::{ - Balance, Balances, CanBondExtra, ExtBuilder, Origin, Pools, Runtime, StakingMock, + Balance, Balances, CanBondExtra, CurrentEra, ExtBuilder, Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, }; use frame_support::{assert_noop, assert_ok}; -// https://stackoverflow.com/questions/27582739/how-do-i-create-a-hashmap-literal -macro_rules! collection { - // map-like - ($($k:expr => $v:expr),* $(,)?) => {{ - use std::iter::{Iterator, IntoIterator}; - Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*])) - }}; - // set-like - ($($v:expr),* $(,)?) => {{ - use std::iter::{Iterator, IntoIterator}; - Iterator::collect(IntoIterator::into_iter([$($v,)*])) - }}; +// TODO +// - make sure any time we do a balance transfer and then some other operation +// we either use transactional storage or have sufficient can_* functions +// - make sure that `unbond` transfers rewards prior to actually unbonding +// - implement staking impl of the delegator pools interface +// - test `do_slash` + +macro_rules! with_era_sub_pools { + ($($k:expr => $v:expr),* $(,)?) => {{ + use sp_std::iter::{Iterator, IntoIterator}; + let not_bounded: BTreeMap<_, _> = Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*])); + SubPoolsWithEra::try_from(not_bounded).unwrap() + }}; } #[test] @@ -52,6 +53,15 @@ fn test_setup_works() { }) } +#[test] +fn exercise_delegator_life_cycle() { + // create pool + // join pool + // claim rewards + // get more rewards + // +} + mod points_to_issue { use super::*; #[test] @@ -1069,8 +1079,8 @@ mod unbond { assert_ok!(Pools::unbond(Origin::signed(10))); assert_eq!( - SubPoolsStorage::::get(0).unwrap().with_era.into_inner(), - collection! { 0 => UnbondPool { points: 10, balance: 10 }} + SubPoolsStorage::::get(0).unwrap().with_era, + with_era_sub_pools! { 0 => UnbondPool:: { points: 10, balance: 10 }} ); assert_eq!( @@ -1095,8 +1105,8 @@ mod unbond { // Then assert_eq!( - SubPoolsStorage::::get(0).unwrap().with_era.into_inner(), - collection! { 0 => UnbondPool { points: 6, balance: 6 }} + SubPoolsStorage::::get(0).unwrap().with_era, + with_era_sub_pools! { 0 => UnbondPool { points: 6, balance: 6 }} ); assert_eq!( BondedPoolStorage::::get(0).unwrap(), @@ -1109,8 +1119,8 @@ mod unbond { // Then assert_eq!( - SubPoolsStorage::::get(0).unwrap().with_era.into_inner(), - collection! { 0 => UnbondPool { points: 7, balance: 7 }} + SubPoolsStorage::::get(0).unwrap().with_era, + with_era_sub_pools! { 0 => UnbondPool { points: 7, balance: 7 }} ); assert_eq!( BondedPoolStorage::::get(0).unwrap(), @@ -1123,8 +1133,8 @@ mod unbond { // Then assert_eq!( - SubPoolsStorage::::get(0).unwrap().with_era.into_inner(), - collection! { 0 => UnbondPool { points: 100, balance: 100 }} + SubPoolsStorage::::get(0).unwrap().with_era, + with_era_sub_pools! { 0 => UnbondPool { points: 100, balance: 100 }} ); assert_eq!( BondedPoolStorage::::get(0).unwrap(), @@ -1134,6 +1144,11 @@ mod unbond { }); } + #[test] + fn unbond_pool_of_3_works_when_there_are_rewards_to_claims() { + todo!() + } + #[test] fn unbond_errors_correctly() { ExtBuilder::default().build_and_execute(|| { @@ -1154,7 +1169,84 @@ mod unbond { } } -mod withdraw_unbonded {} +mod withdraw_unbonded { + use super::*; + + #[test] + fn withdraw_unbonded_works_against_no_era_sub_pool() { + ExtBuilder::default().build_and_execute(|| { + + // storage is cleaned up - delegator is removed + }); + } + + #[test] + fn withdraw_unbonded_works_against_with_era_sub_pools() { + ExtBuilder::default().build_and_execute(|| { + + // storage is cleaned up - delegator is removed + }); + } + + #[test] + fn withdraw_unbonded_errors_correctly() { + ExtBuilder::default().build_and_execute(|| { + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(11)), + Error::::DelegatorNotFound + ); + + let mut delegator = Delegator { + pool: 1, + points: 10, + reward_pool_total_earnings: 0, + unbonding_era: None, + }; + DelegatorStorage::::insert(11, delegator.clone()); + + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(11)), + Error::::NotUnbonding + ); + + delegator.unbonding_era = Some(0); + DelegatorStorage::::insert(11, delegator.clone()); + + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(11)), + Error::::NotUnbondedYet + ); + + CurrentEra::set(StakingMock::bonding_duration()); + + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(11)), + Error::::SubPoolsNotFound + ); + + let sub_pools = SubPools { + no_era: Default::default(), + with_era: with_era_sub_pools! { 0 => UnbondPool { points: 10, balance: 10 }}, + }; + SubPoolsStorage::::insert(1, sub_pools); + + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(11)), + Error::::PoolNotFound + ); + BondedPoolStorage::::insert(1, BondedPool { points: 0, account_id: 123 }); + assert_eq!(Balances::free_balance(&123), 0); + + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(11)), + pallet_balances::Error::::InsufficientBalance + ); + + // The delegator does not get removed if we error + assert_eq!(DelegatorStorage::::get(&11), Some(delegator)); + }); + } +} mod create {} diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 8cd4fd6cd80b7..4aa9292f0303d 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -540,7 +540,7 @@ where active_era: EraIndex, ) -> Balance { if let Some((new_active, new_chunk_balances)) = - P::slash_pool(&self.stash, value, slash_era, active_era) + P::slash_pool(&self.stash, value, slash_era, active_era, self.active) { self.pool_slash(new_active, new_chunk_balances) } else { diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index e5fcba513b29c..9e06d2671f344 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -34,11 +34,28 @@ pub trait PoolsInterface { type AccountId; type Balance; + // The current approach here is to share `BTreeMap>` with the staking + // API. This is arguably a leaky, suboptimal API because both sides have to share this + // non-trivial data structure. With the current design we do this because we track the unbonding + // balance in both the pallet-staking `unlocking` chunks and in here with the pallet-pools + // `SubPools`. Because both pallets need to know about slashes to unbonding funds we either have + // to replicate the slashing logic between the pallets, or share some data. A ALTERNATIVE is + // having the pallet-pools read the unbonding balance per era directly from pallet-staking. The + // downside of this is that once a delegator calls `withdraw_unbonded`, the chunk is removed and + // we can't keep track of the balance for that `UnbondPool` anymore, thus we must merge the + // balance and points of that `UnbondPool` with the `no_era` pool immediately upon calling + // withdraw_unbonded. We choose not to do this because if there was a slash, it would negatively + // affect the points:balance ratio of the `no_era` pool for everyone, including those who may + // not have been unbonding in eras effected by the slash. + /// Calculate the slashes for each unbonding chunk/unbonding pool and the actively bonded + /// balance. This should apply the updated balances to the pools and return the updated balances + /// to the caller (presumably pallet-staking) so they can do the corresponding updates. fn slash_pool( account_id: &Self::AccountId, slash_amount: Self::Balance, slash_era: EraIndex, active_era: EraIndex, + active_bonded: Self::Balance, ) -> Option<(Self::Balance, BTreeMap)>; } From 0622d1dfd76a56b30f5ec9657152d483acacb580 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 25 Jan 2022 16:47:43 -0800 Subject: [PATCH 033/299] Test: withdraw_unbonded_works_against_slashed_with_era_sub_pools --- frame/pools/src/lib.rs | 13 ++++-- frame/pools/src/mock.rs | 14 +++++- frame/pools/src/tests.rs | 84 ++++++++++++++++++++++++++++++----- primitives/staking/src/lib.rs | 2 + 4 files changed, 96 insertions(+), 17 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 329a1f928fbc9..467f5f657de8d 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -600,16 +600,23 @@ pub mod pallet { balance_to_unbond }; - println!("balance_to_unbond {:?}", balance_to_unbond); - let bonded_pool = BondedPoolStorage::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; + + if T::Currency::free_balance(&bonded_pool.account_id) < balance_to_unbond { + T::StakingInterface::withdraw_unbonded(&bonded_pool.account_id)?; + } + T::Currency::transfer( &bonded_pool.account_id, &who, balance_to_unbond, ExistenceRequirement::AllowDeath, - )?; + ) + .map_err(|e| { + log::warn!("system logic error: pool could not withdraw_unbonded due to lack of free balance"); + e + })?; SubPoolsStorage::::insert(delegator.pool, sub_pools); DelegatorStorage::::remove(&who); diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 44161fae2d39e..7f5f2f34d6943 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -6,7 +6,7 @@ use frame_system::RawOrigin; pub type AccountId = u32; pub type Balance = u128; -/// Pool 0's primary account id (i.e. its stash and controller account). +/// Pool 0's primary account id (i.e. its stash and controller account with bonded funds). pub const PRIMARY_ACCOUNT: u32 = 2536596763; /// Pool 0's reward destination. pub const REWARDS_ACCOUNT: u32 = 736857005; @@ -14,6 +14,7 @@ pub const REWARDS_ACCOUNT: u32 = 736857005; parameter_types! { pub static CurrentEra: EraIndex = 0; static BondedBalanceMap: std::collections::HashMap = Default::default(); + static UnbondingBalanceMap: std::collections::HashMap = Default::default(); pub static CanBondExtra: bool = true; } @@ -50,13 +51,22 @@ impl sp_staking::StakingInterface for StakingMock { } fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { - // Simulate bond extra in `join` BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(who).unwrap() += extra); Ok(()) } fn unbond(who: &Self::AccountId, amount: Self::Balance) -> DispatchResult { BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(who).unwrap() -= amount); + UNBONDING_BALANCE_MAP + .with(|m| *m.borrow_mut().entry(*who).or_insert(Self::Balance::zero()) += amount); + Ok(()) + } + + fn withdraw_unbonded(who: &Self::AccountId) -> DispatchResult { + let maybe_new_free = UNBONDING_BALANCE_MAP.with(|m| m.borrow_mut().remove(who)); + if let Some(new_free) = maybe_new_free { + assert_ok!(Balances::mutate_account(who, |a| a.free += new_free)); + } Ok(()) } diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 7eef18913b3b7..28f98afc857e3 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -12,7 +12,7 @@ use frame_support::{assert_noop, assert_ok}; // - implement staking impl of the delegator pools interface // - test `do_slash` -macro_rules! with_era_sub_pools { +macro_rules! sub_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ use sp_std::iter::{Iterator, IntoIterator}; let not_bounded: BTreeMap<_, _> = Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*])); @@ -1080,7 +1080,7 @@ mod unbond { assert_eq!( SubPoolsStorage::::get(0).unwrap().with_era, - with_era_sub_pools! { 0 => UnbondPool:: { points: 10, balance: 10 }} + sub_pools_with_era! { 0 => UnbondPool:: { points: 10, balance: 10 }} ); assert_eq!( @@ -1106,13 +1106,14 @@ mod unbond { // Then assert_eq!( SubPoolsStorage::::get(0).unwrap().with_era, - with_era_sub_pools! { 0 => UnbondPool { points: 6, balance: 6 }} + sub_pools_with_era! { 0 => UnbondPool { points: 6, balance: 6 }} ); assert_eq!( BondedPoolStorage::::get(0).unwrap(), BondedPool { account_id: PRIMARY_ACCOUNT, points: 560 } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 94); + assert_eq!(DelegatorStorage::::get(40).unwrap().unbonding_era, Some(0)); // When assert_ok!(Pools::unbond(Origin::signed(10))); @@ -1120,13 +1121,14 @@ mod unbond { // Then assert_eq!( SubPoolsStorage::::get(0).unwrap().with_era, - with_era_sub_pools! { 0 => UnbondPool { points: 7, balance: 7 }} + sub_pools_with_era! { 0 => UnbondPool { points: 7, balance: 7 }} ); assert_eq!( BondedPoolStorage::::get(0).unwrap(), BondedPool { account_id: PRIMARY_ACCOUNT, points: 550 } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 93); + assert_eq!(DelegatorStorage::::get(10).unwrap().unbonding_era, Some(0)); // When assert_ok!(Pools::unbond(Origin::signed(550))); @@ -1134,16 +1136,22 @@ mod unbond { // Then assert_eq!( SubPoolsStorage::::get(0).unwrap().with_era, - with_era_sub_pools! { 0 => UnbondPool { points: 100, balance: 100 }} + sub_pools_with_era! { 0 => UnbondPool { points: 100, balance: 100 }} ); assert_eq!( BondedPoolStorage::::get(0).unwrap(), BondedPool { account_id: PRIMARY_ACCOUNT, points: 0 } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 0); + assert_eq!(DelegatorStorage::::get(550).unwrap().unbonding_era, Some(0)); }); } + #[test] + fn unbond_merges_older_pools() { + todo!() + } + #[test] fn unbond_pool_of_3_works_when_there_are_rewards_to_claims() { todo!() @@ -1181,11 +1189,61 @@ mod withdraw_unbonded { } #[test] - fn withdraw_unbonded_works_against_with_era_sub_pools() { - ExtBuilder::default().build_and_execute(|| { + fn withdraw_unbonded_works_against_slashed_with_era_sub_pools() { + ExtBuilder::default() + .add_delegators(vec![(40, 40), (550, 550)]) + .build_and_execute(|| { + // Given + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); // slash bonded balance + Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 0); + assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond(Origin::signed(40))); + assert_ok!(Pools::unbond(Origin::signed(550))); + SubPoolsStorage::::insert( + 0, + SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { 0 => UnbondPool { points: 600, balance: 100 }}, + }, + ); + CurrentEra::set(StakingMock::bonding_duration()); - // storage is cleaned up - delegator is removed - }); + // When + assert_ok!(Pools::withdraw_unbonded(Origin::signed(40))); + + // Then + assert_eq!( + SubPoolsStorage::::get(0).unwrap().with_era, + sub_pools_with_era! { 0 => UnbondPool { points: 560, balance: 94 }} + ); + assert_eq!(Balances::free_balance(&40), 40 + 6); + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 94); + assert!(!DelegatorStorage::::contains_key(40)); + + // When + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10))); + + // Then + assert_eq!( + SubPoolsStorage::::get(0).unwrap().with_era, + sub_pools_with_era! { 0 => UnbondPool { points: 550, balance: 93 }} + ); + assert_eq!(Balances::free_balance(&10), 10 + 1); + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 93); + assert!(!DelegatorStorage::::contains_key(10)); + + // When + assert_ok!(Pools::withdraw_unbonded(Origin::signed(550))); + + // Then + assert_eq!( + SubPoolsStorage::::get(0).unwrap().with_era, + sub_pools_with_era! { 0 => UnbondPool { points: 0, balance: 0 }} + ); + assert_eq!(Balances::free_balance(&550), 550 + 93); + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 0); + assert!(!DelegatorStorage::::contains_key(550)); + }); } #[test] @@ -1226,9 +1284,9 @@ mod withdraw_unbonded { let sub_pools = SubPools { no_era: Default::default(), - with_era: with_era_sub_pools! { 0 => UnbondPool { points: 10, balance: 10 }}, + with_era: sub_pools_with_era! { 0 => UnbondPool { points: 10, balance: 10 }}, }; - SubPoolsStorage::::insert(1, sub_pools); + SubPoolsStorage::::insert(1, sub_pools.clone()); assert_noop!( Pools::withdraw_unbonded(Origin::signed(11)), @@ -1242,8 +1300,10 @@ mod withdraw_unbonded { pallet_balances::Error::::InsufficientBalance ); - // The delegator does not get removed if we error + // If we error the delegator does not get removed assert_eq!(DelegatorStorage::::get(&11), Some(delegator)); + // and the subpools do not get updated. + assert_eq!(SubPoolsStorage::::get(1).unwrap(), sub_pools) }); } } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 9e06d2671f344..26dbd65648174 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -91,6 +91,8 @@ pub trait StakingInterface { fn unbond(controller: &Self::AccountId, value: Self::Balance) -> DispatchResult; + fn withdraw_unbonded(controller: &Self::AccountId) -> DispatchResult; + fn bond( stash: Self::AccountId, controller: Self::AccountId, From cee15fb6da042f51f1739e56a830294d1d0113ac Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 25 Jan 2022 18:20:03 -0800 Subject: [PATCH 034/299] Test: withdraw_unbonded_works_against_slashed_no_era_sub_pool --- frame/pools/src/lib.rs | 27 ++++++------ frame/pools/src/mock.rs | 2 +- frame/pools/src/tests.rs | 88 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 96 insertions(+), 21 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 467f5f657de8d..45e90fcb3c729 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -78,9 +78,8 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => { - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) - }, + (true, true) | (false, true) => + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { // The pool was totally slashed. @@ -106,7 +105,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero(); + return Zero::zero() } // Equivalent of (current_balance / current_points) * delegator_points @@ -177,8 +176,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) - < BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) < + BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -267,7 +266,7 @@ impl SubPools { // For the first `0..MaxUnbonding` eras of the chain we don't need to do anything. // I.E. if `MaxUnbonding` is 5 and we are in era 4 we can add a pool for this era and // have exactly `MaxUnbonding` pools. - return self; + return self } // I.E. if `MaxUnbonding` is 5 and current era is 10, we only want to retain pools 6..=10. @@ -283,8 +282,6 @@ impl SubPools { if let Some(p) = self.with_era.remove(&era) { self.no_era.points = self.no_era.points.saturating_add(p.points); self.no_era.balance = self.no_era.balance.saturating_add(p.balance); - } else { - // the world is broken } } @@ -426,7 +423,7 @@ pub mod pallet { // the active balance is slashed below the minimum bonded or the account cannot be // found, we exit early. if !T::StakingInterface::can_bond_extra(&bonded_pool.account_id, amount) { - return Err(Error::::StakingError.into()); + return Err(Error::::StakingError.into()) } // We don't actually care about writing the reward pool, we just need its @@ -577,7 +574,7 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); if current_era.saturating_sub(unbonding_era) < T::StakingInterface::bonding_duration() { - return Err(Error::::NotUnbondedYet.into()); + return Err(Error::::NotUnbondedYet.into()) }; let mut sub_pools = @@ -760,9 +757,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() - || current_points.is_zero() - || reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() || + current_points.is_zero() || + reward_pool.balance.is_zero() { Zero::zero() } else { @@ -854,7 +851,7 @@ impl Pallet { active_bonded_balance.saturating_add(unbonding_affected_balance); if total_affected_balance.is_zero() { - return Some((Zero::zero(), Default::default())); + return Some((Zero::zero(), Default::default())) } if slash_amount > total_affected_balance { // TODO this shouldn't happen as long as MaxBonding pools is greater thant the slash diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 7f5f2f34d6943..2cd30b68375fe 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -21,7 +21,7 @@ parameter_types! { pub struct StakingMock; impl StakingMock { pub(crate) fn set_bonded_balance(who: AccountId, bonded: Balance) { - BONDED_BALANCE_MAP.with(|m| m.borrow_mut().insert(who.clone(), bonded)); + BONDED_BALANCE_MAP.with(|m| m.borrow_mut().insert(who, bonded)); } } diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 28f98afc857e3..88fc6e49ddba4 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1,7 +1,7 @@ use super::*; use crate::mock::{ Balance, Balances, CanBondExtra, CurrentEra, ExtBuilder, Origin, Pools, Runtime, StakingMock, - PRIMARY_ACCOUNT, REWARDS_ACCOUNT, + PRIMARY_ACCOUNT, REWARDS_ACCOUNT, UNBONDING_BALANCE_MAP, }; use frame_support::{assert_noop, assert_ok}; @@ -1181,11 +1181,89 @@ mod withdraw_unbonded { use super::*; #[test] - fn withdraw_unbonded_works_against_no_era_sub_pool() { - ExtBuilder::default().build_and_execute(|| { + fn withdraw_unbonded_works_against_slashed_no_era_sub_pool() { + ExtBuilder::default() + .add_delegators(vec![(40, 40), (550, 550)]) + .build_and_execute(|| { + // Given - // storage is cleaned up - delegator is removed - }); + Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 0); + assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond(Origin::signed(40))); + + let mut current_era = 1; + CurrentEra::set(current_era); + + // In a new era, unbond 550 + assert_ok!(Pools::unbond(Origin::signed(550))); + + // Simulate a slash to the pool with_era(current_era) + let mut sub_pools = SubPoolsStorage::::get(0).unwrap(); + let unbond_pool = sub_pools.with_era.get_mut(¤t_era).unwrap(); + + // Sanity check + assert_eq!(*unbond_pool, UnbondPool { points: 550, balance: 550 }); + + // Decrease the balance by half + unbond_pool.balance = 275; + SubPoolsStorage::::insert(0, sub_pools); + + // Update the equivalent of the unbonding chunks for the `StakingMock` + UNBONDING_BALANCE_MAP + .with(|m| *m.borrow_mut().get_mut(&PRIMARY_ACCOUNT).unwrap() -= 275); + + // Advance the current_era to ensure all `with_era` pools will be merged into `no_era` pool + current_era += ::MaxUnbonding::get(); + CurrentEra::set(current_era); + + // Simulate some other call to unbond that would merge `with_era` pools into `no_era` + let sub_pools = + SubPoolsStorage::::get(0).unwrap().maybe_merge_pools(current_era); + assert_eq!( + sub_pools, + SubPools { + with_era: Default::default(), + no_era: UnbondPool { balance: 275 + 40 + 10, points: 550 + 40 + 10 } + } + ); + SubPoolsStorage::::insert(0, sub_pools); + + // When + assert_ok!(Pools::withdraw_unbonded(Origin::signed(550))); + + // Then + assert_eq!( + SubPoolsStorage::::get(0).unwrap().no_era, + UnbondPool { points: 40 + 10, balance: 275 + 40 + 10 - 297 } + ); + assert_eq!(Balances::free_balance(&550), 550 + 297); + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 275 + 40 + 10 - 297); + assert!(!DelegatorStorage::::contains_key(550)); + + // When + assert_ok!(Pools::withdraw_unbonded(Origin::signed(40))); + + // Then + assert_eq!( + SubPoolsStorage::::get(0).unwrap().no_era, + UnbondPool { points: 10, balance: 28 - 22 } + ); + assert_eq!(Balances::free_balance(&40), 40 + 22); + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 28 - 22); + assert!(!DelegatorStorage::::contains_key(40)); + + // When + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10))); + + // Then + assert_eq!( + SubPoolsStorage::::get(0).unwrap().no_era, + UnbondPool { points: 0, balance: 0 } + ); + assert_eq!(Balances::free_balance(&10), 10 + 6); + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 0); + assert!(!DelegatorStorage::::contains_key(10)); + }); } #[test] From d0d07ff06c38d8b2d03d7fb7f19dd048704425b2 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 25 Jan 2022 18:45:46 -0800 Subject: [PATCH 035/299] Test: unbond_merges_older_pools --- frame/pools/src/tests.rs | 47 ++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 88fc6e49ddba4..822acbaf28cb8 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1099,6 +1099,8 @@ mod unbond { .build_and_execute(|| { // Given a slash from 600 -> 100 StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); + // and unclaimed rewards of 600. + Balances::make_free_balance_be(&REWARDS_ACCOUNT, 600); // When assert_ok!(Pools::unbond(Origin::signed(40))); @@ -1114,6 +1116,7 @@ mod unbond { ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 94); assert_eq!(DelegatorStorage::::get(40).unwrap().unbonding_era, Some(0)); + assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding // When assert_ok!(Pools::unbond(Origin::signed(10))); @@ -1129,6 +1132,7 @@ mod unbond { ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 93); assert_eq!(DelegatorStorage::::get(10).unwrap().unbonding_era, Some(0)); + assert_eq!(Balances::free_balance(&10), 10 + 10); // When assert_ok!(Pools::unbond(Origin::signed(550))); @@ -1144,17 +1148,44 @@ mod unbond { ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 0); assert_eq!(DelegatorStorage::::get(550).unwrap().unbonding_era, Some(0)); + assert_eq!(Balances::free_balance(&550), 550 + 550); }); } #[test] fn unbond_merges_older_pools() { - todo!() - } + ExtBuilder::default().build_and_execute(|| { + // Given + SubPoolsStorage::::insert( + 0, + SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { + 0 => UnbondPool { balance: 10, points: 100 }, + 1 => UnbondPool { balance: 20, points: 20 }, + 2 => UnbondPool { balance: 101, points: 101} + }, + }, + ); - #[test] - fn unbond_pool_of_3_works_when_there_are_rewards_to_claims() { - todo!() + // When + let current_era = 1 + ::MaxUnbonding::get(); + CurrentEra::set(current_era); + + assert_ok!(Pools::unbond(Origin::signed(10))); + + // Then + assert_eq!( + SubPoolsStorage::::get(0).unwrap(), + SubPools { + no_era: UnbondPool { balance: 10 + 20, points: 100 + 20 }, + with_era: sub_pools_with_era! { + 2 => UnbondPool { balance: 101, points: 101}, + current_era => UnbondPool { balance: 10, points: 10 }, + }, + }, + ) + }); } #[test] @@ -1212,11 +1243,13 @@ mod withdraw_unbonded { UNBONDING_BALANCE_MAP .with(|m| *m.borrow_mut().get_mut(&PRIMARY_ACCOUNT).unwrap() -= 275); - // Advance the current_era to ensure all `with_era` pools will be merged into `no_era` pool + // Advance the current_era to ensure all `with_era` pools will be merged into + // `no_era` pool current_era += ::MaxUnbonding::get(); CurrentEra::set(current_era); - // Simulate some other call to unbond that would merge `with_era` pools into `no_era` + // Simulate some other call to unbond that would merge `with_era` pools into + // `no_era` let sub_pools = SubPoolsStorage::::get(0).unwrap().maybe_merge_pools(current_era); assert_eq!( From e234272c5a0bca70d894651f147245ef2fd127b0 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 25 Jan 2022 19:16:46 -0800 Subject: [PATCH 036/299] Test points_to_issue on structs; remove helper fn test --- frame/pools/src/tests.rs | 104 ++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 29 deletions(-) diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 822acbaf28cb8..b12cacc3abde4 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -62,33 +62,6 @@ fn exercise_delegator_life_cycle() { // } -mod points_to_issue { - use super::*; - #[test] - fn points_to_issue_works() { - ExtBuilder::default().build_and_execute(|| { - let points_to_issue = points_to_issue::; - // 1 points : 1 balance ratio - assert_eq!(points_to_issue(100, 100, 10), 10); - assert_eq!(points_to_issue(100, 100, 0), 0); - // 2 points : 1 balance ratio - assert_eq!(points_to_issue(50, 100, 10), 20); - // 1 points: 2 balance ratio - assert_eq!(points_to_issue(100, 50, 10), 5); - // 100 points : 0 balance ratio - assert_eq!(points_to_issue(0, 100, 10), 100 * 10); - // 0 points : 100 balance ratio - assert_eq!(points_to_issue(100, 0, 10), 10); - // 10 points : 3 balance ratio - assert_eq!(points_to_issue(30, 100, 10), 33); - // 2 points : 3 balance ratio - assert_eq!(points_to_issue(300, 200, 10), 6); - // 4 points : 9 balance ratio - assert_eq!(points_to_issue(900, 400, 90), 40) - }); - } -} - mod balance_to_unbond { use super::*; #[test] @@ -119,7 +92,47 @@ mod balance_to_unbond { mod bonded_pool { use super::*; #[test] - fn points_to_issue_works() {} + fn points_to_issue_works() { + let mut bonded_pool = BondedPool:: { points: 100, account_id: PRIMARY_ACCOUNT }; + + // 1 points : 1 balance ratio + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); + assert_eq!(bonded_pool.points_to_issue(10), 10); + assert_eq!(bonded_pool.points_to_issue(0), 0); + + // 2 points : 1 balance ratio + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 50); + assert_eq!(bonded_pool.points_to_issue(10), 20); + + // 1 points : 2 balance ratio + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); + bonded_pool.points = 50; + assert_eq!(bonded_pool.points_to_issue(10), 5); + + // 100 points : 0 balance ratio + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 0); + bonded_pool.points = 100; + assert_eq!(bonded_pool.points_to_issue(10), 100 * 10); + + // 0 points : 100 balance + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); + bonded_pool.points = 100; + assert_eq!(bonded_pool.points_to_issue(10), 10); + + // 10 points : 3 balance ratio + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 30); + assert_eq!(bonded_pool.points_to_issue(10), 33); + + // 2 points : 3 balance ratio + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 300); + bonded_pool.points = 200; + assert_eq!(bonded_pool.points_to_issue(10), 6); + + // 4 points : 9 balance ratio + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 900); + bonded_pool.points = 400; + assert_eq!(bonded_pool.points_to_issue(90), 40); + } #[test] fn balance_to_unbond_works() { @@ -184,9 +197,42 @@ mod reward_pool { } mod unbond_pool { + use super::*; + #[test] fn points_to_issue_works() { - // zero case + // 1 points : 1 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 100 }; + assert_eq!(unbond_pool.points_to_issue(10), 10); + assert_eq!(unbond_pool.points_to_issue(0), 0); + + // 2 points : 1 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 50 }; + assert_eq!(unbond_pool.points_to_issue(10), 20); + + // 1 points : 2 balance ratio + let unbond_pool = UnbondPool:: { points: 50, balance: 100 }; + assert_eq!(unbond_pool.points_to_issue(10), 5); + + // 100 points : 0 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 0 }; + assert_eq!(unbond_pool.points_to_issue(10), 100 * 10); + + // 0 points : 100 balance + let unbond_pool = UnbondPool:: { points: 0, balance: 100 }; + assert_eq!(unbond_pool.points_to_issue(10), 10); + + // 10 points : 3 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 30 }; + assert_eq!(unbond_pool.points_to_issue(10), 33); + + // 2 points : 3 balance ratio + let unbond_pool = UnbondPool:: { points: 200, balance: 300 }; + assert_eq!(unbond_pool.points_to_issue(10), 6); + + // 4 points : 9 balance ratio + let unbond_pool = UnbondPool:: { points: 400, balance: 900 }; + assert_eq!(unbond_pool.points_to_issue(90), 40); } #[test] From c2cd81925cac03964e314cdb391a72982dd632cc Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 25 Jan 2022 19:42:08 -0800 Subject: [PATCH 037/299] Test balance to unbond on structs; remove helper fn tests --- frame/pools/src/tests.rs | 116 +++++++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 40 deletions(-) diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index b12cacc3abde4..8c16960ce6fe8 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -8,9 +8,8 @@ use frame_support::{assert_noop, assert_ok}; // TODO // - make sure any time we do a balance transfer and then some other operation // we either use transactional storage or have sufficient can_* functions -// - make sure that `unbond` transfers rewards prior to actually unbonding // - implement staking impl of the delegator pools interface -// - test `do_slash` +// - test `slash_pool` macro_rules! sub_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ @@ -62,81 +61,87 @@ fn exercise_delegator_life_cycle() { // } -mod balance_to_unbond { - use super::*; - #[test] - fn balance_to_unbond_works() { - ExtBuilder::default().build_and_execute(|| { - let balance_to_unbond = balance_to_unbond::; - // 1 balance : 1 points ratio - assert_eq!(balance_to_unbond(100, 100, 10), 10); - assert_eq!(balance_to_unbond(100, 100, 0), 0); - // 1 balance : 2 points ratio - assert_eq!(balance_to_unbond(50, 100, 10), 5); - // 2 balance : 1 points ratio - assert_eq!(balance_to_unbond(100, 50, 10), 20); - // 100 balance : 0 points ratio - assert_eq!(balance_to_unbond(100, 0, 10), 0); - // 0 balance : 100 points ratio - assert_eq!(balance_to_unbond(0, 100, 10), 0); - // 10 balance : 3 points ratio - assert_eq!(balance_to_unbond(100, 30, 10), 33); - // 2 balance : 3 points ratio - assert_eq!(balance_to_unbond(200, 300, 10), 6); - // 4 balance : 9 points ratio - assert_eq!(balance_to_unbond(400, 900, 90), 40) - }); - } -} - mod bonded_pool { use super::*; #[test] fn points_to_issue_works() { - let mut bonded_pool = BondedPool:: { points: 100, account_id: PRIMARY_ACCOUNT }; + let mut bonded_pool = BondedPool:: { points: 100, account_id: 123 }; // 1 points : 1 balance ratio - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); + StakingMock::set_bonded_balance(123, 100); assert_eq!(bonded_pool.points_to_issue(10), 10); assert_eq!(bonded_pool.points_to_issue(0), 0); // 2 points : 1 balance ratio - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 50); + StakingMock::set_bonded_balance(123, 50); assert_eq!(bonded_pool.points_to_issue(10), 20); // 1 points : 2 balance ratio - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); + StakingMock::set_bonded_balance(123, 100); bonded_pool.points = 50; assert_eq!(bonded_pool.points_to_issue(10), 5); // 100 points : 0 balance ratio - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 0); + StakingMock::set_bonded_balance(123, 0); bonded_pool.points = 100; assert_eq!(bonded_pool.points_to_issue(10), 100 * 10); // 0 points : 100 balance - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); + StakingMock::set_bonded_balance(123, 100); bonded_pool.points = 100; assert_eq!(bonded_pool.points_to_issue(10), 10); // 10 points : 3 balance ratio - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 30); + StakingMock::set_bonded_balance(123, 30); assert_eq!(bonded_pool.points_to_issue(10), 33); // 2 points : 3 balance ratio - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 300); + StakingMock::set_bonded_balance(123, 300); bonded_pool.points = 200; assert_eq!(bonded_pool.points_to_issue(10), 6); // 4 points : 9 balance ratio - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 900); + StakingMock::set_bonded_balance(123, 900); bonded_pool.points = 400; assert_eq!(bonded_pool.points_to_issue(90), 40); } #[test] fn balance_to_unbond_works() { - // zero case + // 1 balance : 1 points ratio + let mut bonded_pool = BondedPool:: { points: 100, account_id: 123 }; + StakingMock::set_bonded_balance(123, 100); + assert_eq!(bonded_pool.balance_to_unbond(10), 10); + assert_eq!(bonded_pool.balance_to_unbond(0), 0); + + // 2 balance : 1 points ratio + bonded_pool.points = 50; + assert_eq!(bonded_pool.balance_to_unbond(10), 20); + + // 100 balance : 0 points ratio + StakingMock::set_bonded_balance(123, 0); + bonded_pool.points = 0; + assert_eq!(bonded_pool.balance_to_unbond(10), 0); + + // 0 balance : 100 points ratio + StakingMock::set_bonded_balance(123, 0); + bonded_pool.points = 100; + assert_eq!(bonded_pool.balance_to_unbond(10), 0); + + // 10 balance : 3 points ratio + StakingMock::set_bonded_balance(123, 100); + bonded_pool.points = 30; + assert_eq!(bonded_pool.balance_to_unbond(10), 33); + + // 2 balance : 3 points ratio + StakingMock::set_bonded_balance(123, 200); + bonded_pool.points = 300; + assert_eq!(bonded_pool.balance_to_unbond(10), 6); + + // 4 balance : 9 points ratio + StakingMock::set_bonded_balance(123, 400); + bonded_pool.points = 900; + assert_eq!(bonded_pool.balance_to_unbond(90), 40); } #[test] @@ -237,7 +242,38 @@ mod unbond_pool { #[test] fn balance_to_unbond_works() { - // zero case + // 1 balance : 1 points ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 100 }; + assert_eq!(unbond_pool.balance_to_unbond(10), 10); + assert_eq!(unbond_pool.balance_to_unbond(0), 0); + + // 1 balance : 2 points ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 50 }; + assert_eq!(unbond_pool.balance_to_unbond(10), 5); + + // 2 balance : 1 points ratio + let unbond_pool = UnbondPool:: { points: 50, balance: 100 }; + assert_eq!(unbond_pool.balance_to_unbond(10), 20); + + // 100 balance : 0 points ratio + let unbond_pool = UnbondPool:: { points: 0, balance: 100 }; + assert_eq!(unbond_pool.balance_to_unbond(10), 0); + + // 0 balance : 100 points ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 0 }; + assert_eq!(unbond_pool.balance_to_unbond(10), 0); + + // 10 balance : 3 points ratio + let unbond_pool = UnbondPool:: { points: 30, balance: 100 }; + assert_eq!(unbond_pool.balance_to_unbond(10), 33); + + // 2 balance : 3 points ratio + let unbond_pool = UnbondPool:: { points: 300, balance: 200 }; + assert_eq!(unbond_pool.balance_to_unbond(10), 6); + + // 4 balance : 9 points ratio + let unbond_pool = UnbondPool:: { points: 900, balance: 400 }; + assert_eq!(unbond_pool.balance_to_unbond(90), 40); } } mod sub_pools { From b85da7fd94259bd58ed8346cf5b81c94a10b7a4d Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:44:40 -0800 Subject: [PATCH 038/299] Test: create --- frame/pools/src/lib.rs | 46 ++++++++----- frame/pools/src/mock.rs | 10 +++ frame/pools/src/tests.rs | 123 +++++++++++++++++++++++++++------- primitives/staking/src/lib.rs | 13 ++++ 4 files changed, 150 insertions(+), 42 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 45e90fcb3c729..69d59d43b0878 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -78,8 +78,9 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), + (true, true) | (false, true) => { + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) + }, (true, false) => { // The pool was totally slashed. @@ -105,7 +106,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero() + return Zero::zero(); } // Equivalent of (current_balance / current_points) * delegator_points @@ -176,8 +177,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) < - BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) + < BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -266,7 +267,7 @@ impl SubPools { // For the first `0..MaxUnbonding` eras of the chain we don't need to do anything. // I.E. if `MaxUnbonding` is 5 and we are in era 4 we can add a pool for this era and // have exactly `MaxUnbonding` pools. - return self + return self; } // I.E. if `MaxUnbonding` is 5 and current era is 10, we only want to retain pools 6..=10. @@ -423,7 +424,7 @@ pub mod pallet { // the active balance is slashed below the minimum bonded or the account cannot be // found, we exit early. if !T::StakingInterface::can_bond_extra(&bonded_pool.account_id, amount) { - return Err(Error::::StakingError.into()) + return Err(Error::::StakingError.into()); } // We don't actually care about writing the reward pool, we just need its @@ -574,7 +575,7 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); if current_era.saturating_sub(unbonding_era) < T::StakingInterface::bonding_duration() { - return Err(Error::::NotUnbondedYet.into()) + return Err(Error::::NotUnbondedYet.into()); }; let mut sub_pools = @@ -642,12 +643,14 @@ pub mod pallet { ensure!(!BondedPoolStorage::::contains_key(id), Error::::IdInUse); ensure!(amount >= T::StakingInterface::minimum_bond(), Error::::MinimumBondNotMet); // TODO create can_* fns so we can bail in the beggining if some pre-conditions are not - // met T::StakingInterface::can_bond() - // T::StakingInterface::can_nominate() let (stash, reward_dest) = Self::create_accounts(id); - T::Currency::transfer(&who, &stash, amount, ExistenceRequirement::AllowDeath)?; + ensure!( + T::StakingInterface::can_nominate(&stash, &targets) + && T::StakingInterface::can_bond(&stash, &stash, &reward_dest), + Error::::StakingError + ); let mut bonded_pool = BondedPool:: { points: Zero::zero(), account_id: stash.clone() }; @@ -657,15 +660,24 @@ pub mod pallet { let points_to_issue = bonded_pool.points_to_issue(amount); bonded_pool.points = points_to_issue; + T::Currency::transfer(&who, &stash, amount, ExistenceRequirement::AllowDeath)?; + T::StakingInterface::bond( stash.clone(), // We make the stash and controller the same for simplicity stash.clone(), amount, reward_dest.clone(), - )?; + ) + .map_err(|e| { + log!(warn, "error trying to bond new pool after a users balance was transferred."); + e + })?; - T::StakingInterface::nominate(stash.clone(), targets)?; + T::StakingInterface::nominate(stash.clone(), targets).map_err(|e| { + log!(warn, "error trying to nominate with a new pool after a users balance was transferred."); + e + })?; DelegatorStorage::::insert( who, @@ -757,9 +769,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() || - current_points.is_zero() || - reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() + || current_points.is_zero() + || reward_pool.balance.is_zero() { Zero::zero() } else { @@ -851,7 +863,7 @@ impl Pallet { active_bonded_balance.saturating_add(unbonding_affected_balance); if total_affected_balance.is_zero() { - return Some((Zero::zero(), Default::default())) + return Some((Zero::zero(), Default::default())); } if slash_amount > total_affected_balance { // TODO this shouldn't happen as long as MaxBonding pools is greater thant the slash diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 2cd30b68375fe..b0ffb7187be2b 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -16,6 +16,8 @@ parameter_types! { static BondedBalanceMap: std::collections::HashMap = Default::default(); static UnbondingBalanceMap: std::collections::HashMap = Default::default(); pub static CanBondExtra: bool = true; + pub static CanBond: bool = true; + pub static CanNominate: bool = true; } pub struct StakingMock; @@ -70,6 +72,10 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } + fn can_bond(_: &Self::AccountId, _: &Self::AccountId, _: &Self::AccountId) -> bool { + CanBond::get() + } + fn bond( stash: Self::AccountId, _: Self::AccountId, @@ -80,6 +86,10 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } + fn can_nominate(_: &Self::AccountId, _: &Vec) -> bool { + CanNominate::get() + } + fn nominate(_: Self::AccountId, _: Vec) -> DispatchResult { Ok(()) } diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 8c16960ce6fe8..310e2a1f2686a 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1,12 +1,16 @@ use super::*; use crate::mock::{ - Balance, Balances, CanBondExtra, CurrentEra, ExtBuilder, Origin, Pools, Runtime, StakingMock, - PRIMARY_ACCOUNT, REWARDS_ACCOUNT, UNBONDING_BALANCE_MAP, + Balance, Balances, CanBond, CanBondExtra, CanNominate, CurrentEra, ExtBuilder, Origin, Pools, + Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, UNBONDING_BALANCE_MAP, }; use frame_support::{assert_noop, assert_ok}; // TODO // - make sure any time we do a balance transfer and then some other operation +// - join - done +// - unbond - not neccesary +// - withdraw_unbonded - not neccesary +// - create - can_bond & can_nominate // we either use transactional storage or have sufficient can_* functions // - implement staking impl of the delegator pools interface // - test `slash_pool` @@ -285,7 +289,7 @@ mod sub_pools { assert_eq!(::MaxUnbonding::get(), 5); // Given - let mut sp0 = SubPools:: { + let mut sub_pool_0 = SubPools:: { no_era: UnbondPool::::default(), with_era: std::collections::BTreeMap::from([ (0, UnbondPool::::new(10, 10)), @@ -299,47 +303,47 @@ mod sub_pools { }; // When `current_era < MaxUnbonding`, - let sp1 = sp0.clone().maybe_merge_pools(3); + let sub_pool_1 = sub_pool_0.clone().maybe_merge_pools(3); // Then it exits early without modifications - assert_eq!(sp1, sp0); + assert_eq!(sub_pool_1, sub_pool_0); // When `current_era == MaxUnbonding`, - let mut sp1 = sp1.maybe_merge_pools(4); + let mut sub_pool_1 = sub_pool_1.maybe_merge_pools(4); // Then it exits early without modifications - assert_eq!(sp1, sp0); + assert_eq!(sub_pool_1, sub_pool_0); // Given we have entries for era 0..=5 - sp1.with_era.insert(5, UnbondPool::::new(50, 50)); - sp0.with_era.insert(5, UnbondPool::::new(50, 50)); + sub_pool_1.with_era.insert(5, UnbondPool::::new(50, 50)); + sub_pool_0.with_era.insert(5, UnbondPool::::new(50, 50)); // When `current_era - MaxUnbonding == 0`, - let sp1 = sp1.maybe_merge_pools(5); + let sub_pool_1 = sub_pool_1.maybe_merge_pools(5); // Then era 0 is merged into the `no_era` pool - sp0.no_era = sp0.with_era.remove(&0).unwrap(); - assert_eq!(sp1, sp0); + sub_pool_0.no_era = sub_pool_0.with_era.remove(&0).unwrap(); + assert_eq!(sub_pool_1, sub_pool_0); // When `current_era - MaxUnbonding == 1` - let sp2 = sp1.maybe_merge_pools(6); - let era_1_pool = sp0.with_era.remove(&1).unwrap(); + let sub_pool_2 = sub_pool_1.maybe_merge_pools(6); + let era_1_pool = sub_pool_0.with_era.remove(&1).unwrap(); // Then era 1 is merged into the `no_era` pool - sp0.no_era.points += era_1_pool.points; - sp0.no_era.balance += era_1_pool.balance; - assert_eq!(sp2, sp0); + sub_pool_0.no_era.points += era_1_pool.points; + sub_pool_0.no_era.balance += era_1_pool.balance; + assert_eq!(sub_pool_2, sub_pool_0); // When `current_era - MaxUnbonding == 5`, so all pools with era <= 4 are removed - let sp3 = sp2.maybe_merge_pools(10); + let sub_pool_3 = sub_pool_2.maybe_merge_pools(10); // Then all eras <= 5 are merged into the `no_era` pool for era in 2..=5 { - let to_merge = sp0.with_era.remove(&era).unwrap(); - sp0.no_era.points += to_merge.points; - sp0.no_era.balance += to_merge.balance; + let to_merge = sub_pool_0.with_era.remove(&era).unwrap(); + sub_pool_0.no_era.points += to_merge.points; + sub_pool_0.no_era.balance += to_merge.balance; } - assert_eq!(sp3, sp0); + assert_eq!(sub_pool_3, sub_pool_0); }); } } @@ -421,8 +425,6 @@ mod join { account_id: 321, }, ); - - // Skipping Currency::transfer & StakingInterface::bond_extra errors }); } } @@ -1501,7 +1503,78 @@ mod withdraw_unbonded { } } -mod create {} +mod create { + use super::*; + + #[test] + fn create_works() { + ExtBuilder::default().build_and_execute(|| { + let bonded_account = 290807105; + + assert!(!BondedPoolStorage::::contains_key(1)); + assert!(!RewardPoolStorage::::contains_key(1)); + assert!(!DelegatorStorage::::contains_key(11)); + assert_eq!(StakingMock::bonded_balance(&bonded_account), 0); + + Balances::make_free_balance_be(&11, StakingMock::minimum_bond()); + assert_ok!(Pools::create(Origin::signed(11), 1, vec![], StakingMock::minimum_bond())); + + assert_eq!(Balances::free_balance(&11), 0); + assert_eq!( + DelegatorStorage::::get(11).unwrap(), + Delegator { + pool: 1, + points: StakingMock::minimum_bond(), + reward_pool_total_earnings: Zero::zero(), + unbonding_era: None + } + ); + assert_eq!( + BondedPoolStorage::::get(1).unwrap(), + BondedPool { points: StakingMock::minimum_bond(), account_id: bonded_account } + ); + assert_eq!(StakingMock::bonded_balance(&bonded_account), StakingMock::minimum_bond()); + assert_eq!( + RewardPoolStorage::::get(1).unwrap(), + RewardPool { + balance: Zero::zero(), + points: U256::zero(), + total_earnings: Zero::zero(), + account_id: 3354943642 + } + ); + }); + } + + #[test] + fn create_errors_correctly() { + ExtBuilder::default().build_and_execute(|| { + assert_noop!( + Pools::create(Origin::signed(11), 0, vec![], 420), + Error::::IdInUse + ); + + assert_noop!( + Pools::create(Origin::signed(11), 1, vec![], 1), + Error::::MinimumBondNotMet + ); + + CanNominate::set(false); + assert_noop!( + Pools::create(Origin::signed(11), 1, vec![], 420), + Error::::StakingError + ); + CanNominate::set(true); + + CanBond::set(false); + assert_noop!( + Pools::create(Origin::signed(11), 1, vec![], 420), + Error::::StakingError + ); + CanBond::set(true); + }); + } +} mod pools_interface { #[test] diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 26dbd65648174..b3e78a424c0e9 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -93,6 +93,15 @@ pub trait StakingInterface { fn withdraw_unbonded(controller: &Self::AccountId) -> DispatchResult; + /// Check if the given accounts can be bonded as stash <-> controller pair and with the given + /// reward destination. Does not check if the accounts have enough funds. It is assumed that the + /// necessary funds will only be transferred into the accounts after this check is completed. + fn can_bond( + stash: &Self::AccountId, + controller: &Self::AccountId, + payee: &Self::AccountId, + ) -> bool; + fn bond( stash: Self::AccountId, controller: Self::AccountId, @@ -100,5 +109,9 @@ pub trait StakingInterface { payee: Self::AccountId, ) -> DispatchResult; + /// Check if the given account can nominate. Assumes the account will be correctly bonded after + /// this call. + fn can_nominate(controller: &Self::AccountId, targets: &Vec) -> bool; + fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult; } From d854bbc435d3d7955b782732ec6b29b8d846958b Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:52:31 -0800 Subject: [PATCH 039/299] Improve test join_works --- frame/pools/src/lib.rs | 29 ++++++++++++++--------------- frame/pools/src/tests.rs | 37 ++++++++++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 69d59d43b0878..13e07ea1923c6 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -78,9 +78,8 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => { - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) - }, + (true, true) | (false, true) => + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { // The pool was totally slashed. @@ -106,7 +105,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero(); + return Zero::zero() } // Equivalent of (current_balance / current_points) * delegator_points @@ -177,8 +176,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) - < BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) < + BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -267,7 +266,7 @@ impl SubPools { // For the first `0..MaxUnbonding` eras of the chain we don't need to do anything. // I.E. if `MaxUnbonding` is 5 and we are in era 4 we can add a pool for this era and // have exactly `MaxUnbonding` pools. - return self; + return self } // I.E. if `MaxUnbonding` is 5 and current era is 10, we only want to retain pools 6..=10. @@ -424,7 +423,7 @@ pub mod pallet { // the active balance is slashed below the minimum bonded or the account cannot be // found, we exit early. if !T::StakingInterface::can_bond_extra(&bonded_pool.account_id, amount) { - return Err(Error::::StakingError.into()); + return Err(Error::::StakingError.into()) } // We don't actually care about writing the reward pool, we just need its @@ -575,7 +574,7 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); if current_era.saturating_sub(unbonding_era) < T::StakingInterface::bonding_duration() { - return Err(Error::::NotUnbondedYet.into()); + return Err(Error::::NotUnbondedYet.into()) }; let mut sub_pools = @@ -647,8 +646,8 @@ pub mod pallet { let (stash, reward_dest) = Self::create_accounts(id); ensure!( - T::StakingInterface::can_nominate(&stash, &targets) - && T::StakingInterface::can_bond(&stash, &stash, &reward_dest), + T::StakingInterface::can_nominate(&stash, &targets) && + T::StakingInterface::can_bond(&stash, &stash, &reward_dest), Error::::StakingError ); @@ -769,9 +768,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() - || current_points.is_zero() - || reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() || + current_points.is_zero() || + reward_pool.balance.is_zero() { Zero::zero() } else { @@ -863,7 +862,7 @@ impl Pallet { active_bonded_balance.saturating_add(unbonding_affected_balance); if total_affected_balance.is_zero() { - return Some((Zero::zero(), Default::default())); + return Some((Zero::zero(), Default::default())) } if slash_amount > total_affected_balance { // TODO this shouldn't happen as long as MaxBonding pools is greater thant the slash diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 310e2a1f2686a..2298e2064a0b0 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1,7 +1,8 @@ use super::*; use crate::mock::{ - Balance, Balances, CanBond, CanBondExtra, CanNominate, CurrentEra, ExtBuilder, Origin, Pools, - Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, UNBONDING_BALANCE_MAP, + Balance, Balances, CanBond, CanBondExtra, CanNominate, CurrentEra, ExistentialDeposit, + ExtBuilder, Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, + UNBONDING_BALANCE_MAP, }; use frame_support::{assert_noop, assert_ok}; @@ -354,13 +355,14 @@ mod join { #[test] fn join_works() { ExtBuilder::default().build_and_execute(|| { - Balances::make_free_balance_be(&11, 5 + 2); - + // Given + Balances::make_free_balance_be(&11, ExistentialDeposit::get() + 2); assert!(!DelegatorStorage::::contains_key(&11)); + // When assert_ok!(Pools::join(Origin::signed(11), 2, 0)); - // Storage is updated correctly + // then assert_eq!( DelegatorStorage::::get(&11).unwrap(), Delegator:: { @@ -374,6 +376,31 @@ mod join { BondedPoolStorage::::get(&0).unwrap(), BondedPool:: { points: 12, account_id: PRIMARY_ACCOUNT } ); + + // Given + // The bonded balance is slashed in half + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 6); + // And + Balances::make_free_balance_be(&12, ExistentialDeposit::get() + 12); + assert!(!DelegatorStorage::::contains_key(&12)); + + // When + assert_ok!(Pools::join(Origin::signed(12), 12, 0)); + + // Then + assert_eq!( + DelegatorStorage::::get(&12).unwrap(), + Delegator:: { + pool: 0, + points: 24, + reward_pool_total_earnings: 0, + unbonding_era: None + } + ); + assert_eq!( + BondedPoolStorage::::get(&0).unwrap(), + BondedPool:: { points: 12 + 24, account_id: PRIMARY_ACCOUNT } + ); }); } From d6af544f09970d4ff92f5e66003d4e2420158c87 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 26 Jan 2022 14:45:16 -0800 Subject: [PATCH 040/299] Initial StakingInterface impl for pallet-staking --- frame/pools/src/lib.rs | 10 +- frame/pools/src/mock.rs | 4 +- frame/pools/src/tests.rs | 8 +- frame/staking/src/pallet/impls.rs | 173 +++++++++++++++++++++++++++--- frame/staking/src/pallet/mod.rs | 37 ++++--- frame/system/src/lib.rs | 15 +++ primitives/staking/src/lib.rs | 18 ++-- 7 files changed, 214 insertions(+), 51 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 13e07ea1923c6..c745c37c0125d 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -147,19 +147,22 @@ pub struct BondedPool { impl BondedPool { /// Get the amount of points to issue for some new funds that will be bonded in the pool. fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { - let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); + let bonded_balance = + T::StakingInterface::bonded_balance(&self.account_id).unwrap_or(Zero::zero()); points_to_issue::(bonded_balance, self.points, new_funds) } // Get the amount of balance to unbond from the pool based on a delegator's points of the pool. fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { - let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); + let bonded_balance = + T::StakingInterface::bonded_balance(&self.account_id).unwrap_or(Zero::zero()); balance_to_unbond::(bonded_balance, self.points, delegator_points) } // Check that the pool can accept a member with `new_funds`. fn ok_to_join_with(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { - let bonded_balance = T::StakingInterface::bonded_balance(&self.account_id); + let bonded_balance = + T::StakingInterface::bonded_balance(&self.account_id).unwrap_or(Zero::zero()); ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); let points_to_balance_ratio_floor = self @@ -531,6 +534,7 @@ pub mod pallet { // to unbond so we have the correct points for the balance:share ratio. bonded_pool.points = bonded_pool.points.saturating_sub(delegator.points); + // TODO: call withdraw unbonded to try and minimize unbonding chunks // Unbond in the actual underlying pool T::StakingInterface::unbond(&bonded_pool.account_id, balance_to_unbond)?; diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index b0ffb7187be2b..7963c87dad88d 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -44,8 +44,8 @@ impl sp_staking::StakingInterface for StakingMock { 3 } - fn bonded_balance(who: &Self::AccountId) -> Self::Balance { - BondedBalanceMap::get().get(who).map(|v| *v).unwrap_or_default() + fn bonded_balance(who: &Self::AccountId) -> Option { + BondedBalanceMap::get().get(who).map(|v| *v) } fn can_bond_extra(_: &Self::AccountId, _: Self::Balance) -> bool { diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 2298e2064a0b0..d1ddb86b4098c 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -6,13 +6,7 @@ use crate::mock::{ }; use frame_support::{assert_noop, assert_ok}; -// TODO -// - make sure any time we do a balance transfer and then some other operation -// - join - done -// - unbond - not neccesary -// - withdraw_unbonded - not neccesary -// - create - can_bond & can_nominate -// we either use transactional storage or have sufficient can_* functions +// // - implement staking impl of the delegator pools interface // - test `slash_pool` diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 94e824f2364d4..4501a3440e2bb 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -29,15 +29,15 @@ use frame_support::{ }, weights::{Weight, WithPostDispatchInfo}, }; -use frame_system::pallet_prelude::BlockNumberFor; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_session::historical; use sp_runtime::{ - traits::{Bounded, Convert, SaturatedConversion, Saturating, Zero}, + traits::{Bounded, Convert, SaturatedConversion, Saturating, StaticLookup, Zero}, Perbill, }; use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - EraIndex, SessionIndex, + EraIndex, SessionIndex, StakingInterface, }; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; @@ -143,7 +143,7 @@ impl Pallet { // Nothing to do if they have no reward points. if validator_reward_points.is_zero() { - return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into()) + return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into()); } // This is the fraction of the total reward that the validator and the @@ -235,8 +235,9 @@ impl Pallet { Self::update_ledger(&controller, &l); r }), - RewardDestination::Account(dest_account) => - Some(T::Currency::deposit_creating(&dest_account, amount)), + RewardDestination::Account(dest_account) => { + Some(T::Currency::deposit_creating(&dest_account, amount)) + }, RewardDestination::None => None, } } @@ -264,14 +265,14 @@ impl Pallet { _ => { // Either `Forcing::ForceNone`, // or `Forcing::NotForcing if era_length >= T::SessionsPerEra::get()`. - return None + return None; }, } // New era. let maybe_new_era_validators = Self::try_trigger_new_era(session_index, is_genesis); - if maybe_new_era_validators.is_some() && - matches!(ForceEra::::get(), Forcing::ForceNew) + if maybe_new_era_validators.is_some() + && matches!(ForceEra::::get(), Forcing::ForceNew) { ForceEra::::put(Forcing::NotForcing); } @@ -462,7 +463,7 @@ impl Pallet { } Self::deposit_event(Event::StakingElectionFailed); - return None + return None; } Self::deposit_event(Event::StakersElected); @@ -869,7 +870,7 @@ impl ElectionDataProvider for Pallet { // We can't handle this case yet -- return an error. if maybe_max_len.map_or(false, |max_len| target_count > max_len as u32) { - return Err("Target snapshot too big") + return Err("Target snapshot too big"); } Ok(Self::get_npos_targets()) @@ -1138,7 +1139,7 @@ where add_db_reads_writes(1, 0); if active_era.is_none() { // This offence need not be re-submitted. - return consumed_weight + return consumed_weight; } active_era.expect("value checked not to be `None`; qed").index }; @@ -1184,7 +1185,7 @@ where // Skip if the validator is invulnerable. if invulnerables.contains(stash) { - continue + continue; } let unapplied = slashing::compute_slash::(slashing::SlashParams { @@ -1308,3 +1309,149 @@ impl SortedListProvider for UseNominatorsMap { Nominators::::remove_all(); } } + +impl StakingInterface for Pallet { + type AccountId = T::AccountId; + type Balance = BalanceOf; + type LookupSource = ::Source; + + fn minimum_bond() -> Self::Balance { + MinNominatorBond::::get() + } + + fn bonding_duration() -> EraIndex { + T::BondingDuration::get() + } + + fn current_era() -> Option { + Self::current_era() + } + + fn bonded_balance(controller: &Self::AccountId) -> Option { + Self::ledger(controller).map(|l| l.active) + } + + fn can_bond_extra(controller: &Self::AccountId, extra: Self::Balance) -> bool { + let ledger = match Self::ledger(&controller) { + Some(l) => l, + None => return false, + }; + + if ledger.active.saturating_add(extra) < T::Currency::minimum_balance() { + false + } else { + true + } + } + + fn bond_extra(stash: Self::AccountId, extra: Self::Balance) -> DispatchResult { + Self::bond_extra(RawOrigin::Signed(stash).into(), extra) + } + + fn unbond(controller: Self::AccountId, value: Self::Balance) -> DispatchResult { + Self::unbond(RawOrigin::Signed(controller).into(), value) + } + + fn withdraw_unbonded( + controller: Self::AccountId, + stash: &Self::AccountId, + ) -> Result { + // TODO should probably just make this an input param + let num_slashing_spans = match as Store>::SlashingSpans::get(stash) { + None => 0, + Some(s) => s.iter().count() as u32, + }; + + Self::withdraw_unbonded(RawOrigin::Signed(controller).into(), num_slashing_spans) + .map(|post_info| { + post_info + .actual_weight + .unwrap_or(T::WeightInfo::withdraw_unbonded_kill(num_slashing_spans)) + }) + .map_err(|err_with_post_info| err_with_post_info.error) + } + + fn can_bond( + stash: &Self::AccountId, + controller: &Self::AccountId, + value: Self::Balance, + _: &Self::AccountId, + ) -> bool { + if Bonded::::contains_key(stash) { + return false; + } + + if Ledger::::contains_key(controller) { + return false; + } + + if value < T::Currency::minimum_balance() { + return false; + } + + if !frame_system::Pallet::::can_inc_consumers(stash) { + return false; + } + + true + } + + fn bond( + stash: Self::AccountId, + controller: Self::AccountId, + value: Self::Balance, + payee: Self::AccountId, + ) -> DispatchResult { + Self::bond( + RawOrigin::Signed(stash).into(), + T::Lookup::unlookup(controller), + value, + RewardDestination::Account(payee), + ) + } + + fn can_nominate(controller: &Self::AccountId, targets: &Vec) -> bool { + let ledger = match Self::ledger(&controller) { + Some(l) => l, + None => return false, + }; + let stash = &ledger.stash; + + if ledger.active < MinNominatorBond::::get() { + return false; + } + + if !Nominators::::contains_key(stash) { + if let Some(max_nominators) = MaxNominatorsCount::::get() { + if Nominators::::count() < max_nominators { + return false; + }; + } + } + + if targets.is_empty() { + return false; + } + + if targets.len() as u32 > T::MAX_NOMINATIONS { + return false; + } + + let old = Nominators::::get(stash).map_or_else(Vec::new, |x| x.targets); + for validator in targets { + if let Ok(validator) = T::Lookup::lookup(validator.clone()) { + if !(old.contains(&validator) || !Validators::::get(&validator).blocked) { + return false; + } + } else { + return false; + } + } + + true + } + + fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult { + Self::nominate(RawOrigin::Signed(controller).into(), targets) + } +} diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 1cf6b56152a98..d10c8e31bc3c9 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -933,24 +933,23 @@ pub mod pallet { ledger = ledger.consolidate_unlocked(current_era) } - let post_info_weight = if ledger.unlocking.is_empty() && - ledger.active < T::Currency::minimum_balance() - { - // This account must have called `unbond()` with some value that caused the active - // portion to fall below existential deposit + will have no more unlocking chunks - // left. We can now safely remove all staking-related information. - Self::kill_stash(&stash, num_slashing_spans)?; - // Remove the lock. - T::Currency::remove_lock(STAKING_ID, &stash); - // This is worst case scenario, so we use the full weight and return None - None - } else { - // This was the consequence of a partial unbond. just update the ledger and move on. - Self::update_ledger(&controller, &ledger); + let post_info_weight = + if ledger.unlocking.is_empty() && ledger.active < T::Currency::minimum_balance() { + // This account must have called `unbond()` with some value that caused the active + // portion to fall below existential deposit + will have no more unlocking chunks + // left. We can now safely remove all staking-related information. + Self::kill_stash(&stash, num_slashing_spans)?; + // Remove the lock. + T::Currency::remove_lock(STAKING_ID, &stash); + // This is worst case scenario, so we use the full weight and return None + None + } else { + // This was the consequence of a partial unbond. just update the ledger and move on. + Self::update_ledger(&controller, &ledger); - // This is only an update, so we use less overall weight. - Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) - }; + // This is only an update, so we use less overall weight. + Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) + }; // `old_total` should never be less than the new total because // `consolidate_unlocked` strictly subtracts balance. @@ -1476,8 +1475,8 @@ pub mod pallet { let _ = ensure_signed(origin)?; let ed = T::Currency::minimum_balance(); - let reapable = T::Currency::total_balance(&stash) < ed || - Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) + let reapable = T::Currency::total_balance(&stash) < ed + || Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) .map(|l| l.total) .unwrap_or_default() < ed; ensure!(reapable, Error::::FundedTarget); diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 7615424ba57ee..9a768eadefc54 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -1134,6 +1134,21 @@ impl Pallet { a.providers + a.sufficients } + /// Check if the reference counter on an account can be incremented. + pub fn can_inc_consumers(who: &T::AccountId) -> bool { + let account = Account::::get(who); + + if account.providers > 0 { + if account.consumers < T::MaxConsumers::max_consumers() { + true + } else { + false + } + } else { + false + } + } + /// Increment the reference counter on an account. /// /// The account `who`'s `providers` must be non-zero and the current number of consumers must diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index b3e78a424c0e9..b106eee34d3e0 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -19,7 +19,7 @@ //! A crate which contains primitives that are useful for implementation that uses staking //! approaches in general. Definitions related to sessions, slashing, etc go here. -use sp_runtime::DispatchResult; +use sp_runtime::{DispatchError, DispatchResult}; use sp_std::collections::btree_map::BTreeMap; pub mod offence; @@ -78,20 +78,23 @@ pub trait StakingInterface { fn bonding_duration() -> EraIndex; /// The current era for the staking system. - fn current_era() -> EraIndex; + fn current_era() -> Option; /// Balance `controller` has bonded for nominating. - fn bonded_balance(controller: &Self::AccountId) -> Self::Balance; + fn bonded_balance(controller: &Self::AccountId) -> Option; /// If the given staker can successfully call `bond_extra` with `extra`. Assumes the `extra` /// balance will be transferred in the stash. fn can_bond_extra(controller: &Self::AccountId, extra: Self::Balance) -> bool; - fn bond_extra(controller: &Self::AccountId, extra: Self::Balance) -> DispatchResult; + fn bond_extra(controller: Self::AccountId, extra: Self::Balance) -> DispatchResult; - fn unbond(controller: &Self::AccountId, value: Self::Balance) -> DispatchResult; + fn unbond(controller: Self::AccountId, value: Self::Balance) -> DispatchResult; - fn withdraw_unbonded(controller: &Self::AccountId) -> DispatchResult; + fn withdraw_unbonded( + controller: Self::AccountId, + stash: &Self::AccountId, + ) -> Result; /// Check if the given accounts can be bonded as stash <-> controller pair and with the given /// reward destination. Does not check if the accounts have enough funds. It is assumed that the @@ -99,13 +102,14 @@ pub trait StakingInterface { fn can_bond( stash: &Self::AccountId, controller: &Self::AccountId, + value: Self::Balance, payee: &Self::AccountId, ) -> bool; fn bond( stash: Self::AccountId, controller: Self::AccountId, - amount: Self::Balance, + value: Self::Balance, payee: Self::AccountId, ) -> DispatchResult; From ef107b64b6d5fcd097d62794453f8e5cc36fdf68 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 26 Jan 2022 15:39:16 -0800 Subject: [PATCH 041/299] Test: can_nominate_passes_valid_inputs --- frame/pools/src/tests.rs | 2 +- frame/staking/src/mock.rs | 22 ++++++++-- frame/staking/src/pallet/mod.rs | 37 ++++++++-------- frame/staking/src/tests.rs | 77 ++++++++++++++++++++++++++------- 4 files changed, 100 insertions(+), 38 deletions(-) diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index d1ddb86b4098c..ac3a519d99d4d 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -6,7 +6,7 @@ use crate::mock::{ }; use frame_support::{assert_noop, assert_ok}; -// +// // - implement staking impl of the delegator pools interface // - test `slash_pool` diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 3bf46588044a6..625ff327dad8e 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -82,6 +82,21 @@ pub fn is_disabled(controller: AccountId) -> bool { Session::disabled_validators().contains(&validator_index) } +pub struct PoolsInterfaceMock; +impl sp_staking::PoolsInterface for PoolsInterfaceMock { + type AccountId = AccountId; + type Balance = Balance; + fn slash_pool( + _: &Self::AccountId, + _: Self::Balance, + _: EraIndex, + _: EraIndex, + _: Self::Balance, + ) -> Option<(Self::Balance, BTreeMap)> { + None + } +} + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -271,6 +286,7 @@ impl crate::pallet::pallet::Config for Test { // NOTE: consider a macro and use `UseNominatorsMap` as well. type SortedListProvider = BagsList; type BenchmarkingConfig = TestBenchmarkingConfig; + type PoolsInterface = PoolsInterfaceMock; type WeightInfo = (); } @@ -293,7 +309,7 @@ pub struct ExtBuilder { invulnerables: Vec, has_stakers: bool, initialize_first_session: bool, - min_nominator_bond: Balance, + pub min_nominator_bond: Balance, min_validator_bond: Balance, balance_factor: Balance, status: BTreeMap>, @@ -772,9 +788,9 @@ pub(crate) fn on_offence_in_era( for &(bonded_era, start_session) in bonded_eras.iter() { if bonded_era == era { let _ = Staking::on_offence(offenders, slash_fraction, start_session, disable_strategy); - return + return; } else if bonded_era > era { - break + break; } } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index d10c8e31bc3c9..1cf6b56152a98 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -933,23 +933,24 @@ pub mod pallet { ledger = ledger.consolidate_unlocked(current_era) } - let post_info_weight = - if ledger.unlocking.is_empty() && ledger.active < T::Currency::minimum_balance() { - // This account must have called `unbond()` with some value that caused the active - // portion to fall below existential deposit + will have no more unlocking chunks - // left. We can now safely remove all staking-related information. - Self::kill_stash(&stash, num_slashing_spans)?; - // Remove the lock. - T::Currency::remove_lock(STAKING_ID, &stash); - // This is worst case scenario, so we use the full weight and return None - None - } else { - // This was the consequence of a partial unbond. just update the ledger and move on. - Self::update_ledger(&controller, &ledger); + let post_info_weight = if ledger.unlocking.is_empty() && + ledger.active < T::Currency::minimum_balance() + { + // This account must have called `unbond()` with some value that caused the active + // portion to fall below existential deposit + will have no more unlocking chunks + // left. We can now safely remove all staking-related information. + Self::kill_stash(&stash, num_slashing_spans)?; + // Remove the lock. + T::Currency::remove_lock(STAKING_ID, &stash); + // This is worst case scenario, so we use the full weight and return None + None + } else { + // This was the consequence of a partial unbond. just update the ledger and move on. + Self::update_ledger(&controller, &ledger); - // This is only an update, so we use less overall weight. - Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) - }; + // This is only an update, so we use less overall weight. + Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) + }; // `old_total` should never be less than the new total because // `consolidate_unlocked` strictly subtracts balance. @@ -1475,8 +1476,8 @@ pub mod pallet { let _ = ensure_signed(origin)?; let ed = T::Currency::minimum_balance(); - let reapable = T::Currency::total_balance(&stash) < ed - || Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) + let reapable = T::Currency::total_balance(&stash) < ed || + Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) .map(|l| l.total) .unwrap_or_default() < ed; ensure!(reapable, Error::::FundedTarget); diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 8d465c8c93dc4..27fb058c91ccc 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -275,9 +275,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 + - part_for_100_from_10 * total_payout_0 * 2 / 3 + - part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * total_payout_0 * 2 / 3 + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -313,9 +313,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 + - part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) + - part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -3967,8 +3967,8 @@ mod election_data_provider { #[test] fn targets_2sec_block() { let mut validators = 1000; - while ::WeightInfo::get_npos_targets(validators) < - 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_targets(validators) + < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { validators += 1; } @@ -3985,8 +3985,8 @@ mod election_data_provider { let slashing_spans = validators; let mut nominators = 1000; - while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) < - 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) + < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { nominators += 1; } @@ -4057,8 +4057,8 @@ mod election_data_provider { .build_and_execute(|| { // sum of all nominators who'd be voters (1), plus the self-votes (4). assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 5 ); @@ -4097,8 +4097,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 7 ); @@ -4142,8 +4142,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 6 ); @@ -4613,3 +4613,48 @@ mod sorted_list_provider { }); } } + +mod staking_interface { + use super::*; + use sp_staking::StakingInterface as _; + + #[test] + fn can_nominate_passes_valid_inputs() { + ExtBuilder::default().build_and_execute(|| { + assert_ok!(Staking::bond( + Origin::signed(80), + 81, + 1, + RewardDestination::Controller + )); + + let targets = vec![11]; + assert!(Staking::can_nominate(&81, &targets)); + }); + } + + #[test] + fn can_nominate_fails_invalid_inputs() { + todo!() + } + + #[test] + fn can_bond_passes_valid_inputs() { + todo!() + } + + #[test] + fn can_bond_fails_invalid_inputs() { + todo!() + } + + #[test] + fn can_bond_extra_passes_valid_inputs() { + todo!() + } + + #[test] + fn can_bond_extra_fails_invalid_inputs() { + todo!() + } +} From c3cbdc861785545ed67640be49f6b1f454fa8eb7 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 26 Jan 2022 17:08:02 -0800 Subject: [PATCH 042/299] Test: can_nominate_fails_invalid_inputs --- frame/pools/src/tests.rs | 1 + frame/staking/src/pallet/impls.rs | 2 +- frame/staking/src/tests.rs | 71 ++++++++++++++++++++++++++++--- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index ac3a519d99d4d..5b5d1413f7170 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -9,6 +9,7 @@ use frame_support::{assert_noop, assert_ok}; // // - implement staking impl of the delegator pools interface // - test `slash_pool` +// - incorporate returned weight from staking calls macro_rules! sub_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 4501a3440e2bb..90adeaa12e1a1 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1423,7 +1423,7 @@ impl StakingInterface for Pallet { if !Nominators::::contains_key(stash) { if let Some(max_nominators) = MaxNominatorsCount::::get() { - if Nominators::::count() < max_nominators { + if Nominators::::count() > max_nominators { return false; }; } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 27fb058c91ccc..73fa9eb65f4c0 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4621,21 +4621,78 @@ mod staking_interface { #[test] fn can_nominate_passes_valid_inputs() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(Staking::bond( - Origin::signed(80), - 81, - 1, - RewardDestination::Controller - )); + assert_ok!(Staking::bond(Origin::signed(80), 81, 1, RewardDestination::Controller)); let targets = vec![11]; assert!(Staking::can_nominate(&81, &targets)); + + // 11 blocks + assert_ok!(Staking::validate( + Origin::signed(10), + ValidatorPrefs { blocked: true, ..Default::default() } + )); + // but we can still nominate them since they are in the old nominations + assert!(Staking::can_nominate(&81, &targets)); + + // The nominator count limit is set to 0 + MaxNominatorsCount::::set(Some(0)); + // but we can still nominate because we have pre-existing nomination + assert!(Staking::can_nominate(&81, &targets)); + + // They can still nominate with exactly `MAX_NOMINATIONS + let targets: Vec<_> = (0..Test::MAX_NOMINATIONS).map(|i| i as u64).collect(); + assert!(Staking::can_nominate(&81, &targets)); }); } #[test] fn can_nominate_fails_invalid_inputs() { - todo!() + ExtBuilder::default() + .existential_deposit(100) + .balance_factor(100) + .min_nominator_bond(1_000) + .build_and_execute(|| { + let targets = vec![11]; + + // Not bonded, so no ledger + assert_eq!(Staking::can_nominate(&80, &targets), false); + + // Bonded, but below min bond to nominate + assert_ok!(Staking::bond( + Origin::signed(81), + 80, + 1_000 - 10, + RewardDestination::Controller + )); + assert_eq!(Staking::can_nominate(&80, &targets), false); + + // Meets min bond, but already at nominator limit + assert_ok!(Staking::bond( + Origin::signed(71), + 70, + 1_000 + 10, + RewardDestination::Controller + )); + MaxNominatorsCount::::set(Some(0)); + assert_eq!(Staking::can_nominate(&70, &targets), false); + MaxNominatorsCount::::set(None); + + // Targets are empty + let targets = vec![]; + assert_eq!(Staking::can_nominate(&70, &targets), false); + + // Too many targets + let targets: Vec<_> = (0..=Test::MAX_NOMINATIONS).map(|i| i as u64).collect(); + assert_eq!(Staking::can_nominate(&70, &targets), false); + + // A target is blocking + assert_ok!(Staking::validate( + Origin::signed(10), + ValidatorPrefs { blocked: true, ..Default::default() } + )); + let targets = vec![11]; + assert_eq!(Staking::can_nominate(&70, &targets), false); + }); } #[test] From 4061357e78adeccdc263713e1030290c54ef5db8 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 26 Jan 2022 17:48:47 -0800 Subject: [PATCH 043/299] Test: can_bond_works --- frame/staking/src/pallet/impls.rs | 2 +- frame/staking/src/tests.rs | 31 +++++++++++++++++++++++++------ frame/system/src/lib.rs | 15 --------------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 90adeaa12e1a1..78549369eab24 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1389,7 +1389,7 @@ impl StakingInterface for Pallet { return false; } - if !frame_system::Pallet::::can_inc_consumers(stash) { + if !frame_system::Pallet::::can_inc_consumer(stash) { return false; } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 73fa9eb65f4c0..a659493b1c7bf 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4696,13 +4696,32 @@ mod staking_interface { } #[test] - fn can_bond_passes_valid_inputs() { - todo!() - } + fn can_bond_works() { + ExtBuilder::default() + .existential_deposit(100) + .balance_factor(100) + .nominate(false) + .build_and_execute(|| { + // Amount to bond does not meet ED + assert_eq!(Staking::can_bond(&61, &60, 99, &0), false); - #[test] - fn can_bond_fails_invalid_inputs() { - todo!() + // A ledger already exists for the given controller + Ledger::::insert(60, StakingLedger::default_from(61)); + assert_eq!(Staking::can_bond(&61, &60, 100, &0), false); + + // The stash is already bonded to a controller + Bonded::::insert(71, 70); + assert_eq!(Staking::can_bond(&71, &70, 100, &0), false); + + // Cannot increment consumers for stash + frame_system::Account::::mutate(&81, |a| { + a.providers = 0; + }); + assert_eq!(Staking::can_bond(&81, &80, 100, &0), false); + + // Works with valid inputs + assert!(Staking::can_bond(&101, &100, 100, &0)) + }); } #[test] diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 9a768eadefc54..7615424ba57ee 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -1134,21 +1134,6 @@ impl Pallet { a.providers + a.sufficients } - /// Check if the reference counter on an account can be incremented. - pub fn can_inc_consumers(who: &T::AccountId) -> bool { - let account = Account::::get(who); - - if account.providers > 0 { - if account.consumers < T::MaxConsumers::max_consumers() { - true - } else { - false - } - } else { - false - } - } - /// Increment the reference counter on an account. /// /// The account `who`'s `providers` must be non-zero and the current number of consumers must From bab8e4a7112aa09092acf6d23ad1face8b0a6a7e Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 26 Jan 2022 18:01:46 -0800 Subject: [PATCH 044/299] Test: can_bond_extra_works --- frame/staking/src/tests.rs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index a659493b1c7bf..7451321719624 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4725,12 +4725,33 @@ mod staking_interface { } #[test] - fn can_bond_extra_passes_valid_inputs() { - todo!() - } + fn can_bond_extra_works() { + ExtBuilder::default() + .existential_deposit(100) + .balance_factor(100) + .build_and_execute(|| { + // The account is not bonded in the first place + assert_eq!(Staking::can_bond_extra(&60, 100), false); - #[test] - fn can_bond_extra_fails_invalid_inputs() { - todo!() + // The account is bonded + assert_ok!(Staking::bond( + Origin::signed(61), + 60, + 100, + RewardDestination::Controller + )); + // but the active balance of the account is too low + Ledger::::mutate(60, |l| l.as_mut().unwrap().active = 50); + assert_eq!(Staking::can_bond_extra(&60, 49), false); + + // The account is bonded and will have an ok active balance + assert!(Staking::can_bond_extra(&60, 50)); + + // Clean up so post checks pass + Ledger::::mutate(60, |l| l.as_mut().unwrap().active = 100); + }); } + + // TODO: probably should test all other fns of the interface impl? Although benchmarks should + // at least make sure those work on the happy path } From 5e161e807d536c06a47225ba7f4a1d1c2623224d Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 27 Jan 2022 10:02:35 -0800 Subject: [PATCH 045/299] Add WithEraWithdrawWindow to config --- frame/pools/src/lib.rs | 47 ++++++++++++++++++++----------- frame/pools/src/mock.rs | 40 +++++++++++++++----------- frame/pools/src/tests.rs | 25 +++++++++------- frame/staking/src/mock.rs | 4 +-- frame/staking/src/pallet/impls.rs | 42 +++++++++++++-------------- frame/staking/src/tests.rs | 32 ++++++++++----------- primitives/staking/src/lib.rs | 2 ++ 7 files changed, 110 insertions(+), 82 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index c745c37c0125d..40dbc7f0583de 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -5,7 +5,7 @@ //! * bonded pool: This pool represents the actively staked funds ... //! * rewards pool: The rewards earned by actively staked funds. Delegator can withdraw rewards once //! * sub pools: This a group of pools where we have a set of pools organized by era -//! (`SubPools.with_era`) and one pool that is not associated with an era (`SubPools.no_era`). +//! (`SubPools::with_era`) and one pool that is not associated with an era (`SubPools.no_era`). //! Once a `with_era` pool is older then `current_era - MaxUnbonding`, its points and balance get //! merged into the `no_era` pool. //! @@ -64,7 +64,7 @@ macro_rules! log { type PoolId = u32; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -type SubPoolsWithEra = BoundedBTreeMap, ::MaxUnbonding>; +type SubPoolsWithEra = BoundedBTreeMap, MaxUnbonding>; // NOTE: this assumes the balance type u128 or smaller. type RewardPoints = U256; @@ -265,7 +265,7 @@ impl SubPools { /// Merge the oldest unbonding pool with an era into the general unbond pool with no associated /// era. fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self { - if current_era < T::MaxUnbonding::get().into() { + if current_era < MaxUnbonding::::get().into() { // For the first `0..MaxUnbonding` eras of the chain we don't need to do anything. // I.E. if `MaxUnbonding` is 5 and we are in era 4 we can add a pool for this era and // have exactly `MaxUnbonding` pools. @@ -273,7 +273,7 @@ impl SubPools { } // I.E. if `MaxUnbonding` is 5 and current era is 10, we only want to retain pools 6..=10. - let newest_era_to_remove = current_era.saturating_sub(T::MaxUnbonding::get()); + let newest_era_to_remove = current_era.saturating_sub(MaxUnbonding::::get()); let eras_to_remove: Vec<_> = self .with_era @@ -292,6 +292,16 @@ impl SubPools { } } +/// The maximum amount of eras an unbonding pool can exist prior to being merged with the +/// `no_era pool. This is guaranteed to at least be equal to the staking `UnbondingDuration`. For +/// improved UX [`Config::WithEraWithdrawWindow`] should be configured to a non-zero value. +struct MaxUnbonding(PhantomData); +impl Get for MaxUnbonding { + fn get() -> u32 { + T::StakingInterface::bonding_duration() + T::WithEraWithdrawWindow::get() + } +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -326,11 +336,12 @@ pub mod pallet { LookupSource = ::Source, >; - /// The maximum amount of eras an unbonding pool can exist prior to being merged with the - /// `no_era pool. This should at least be greater then the `UnbondingDuration` for staking - /// so delegator have a chance to withdraw unbonded before their pool gets merged with the - /// `no_era` pool. This *must* at least be greater then the slash deffer duration. - type MaxUnbonding: Get; + /// The amount of eras a `SubPools::with_era` pool can exist before it gets merged into the + /// `SubPools::no_era` pool. In other words, this is the amount of eras a delegator will be + /// able to withdraw from an unbonding pool which is guaranteed to have the correct ratio of + /// points to balance; once the `with_era` pool is merged into the `no_era` pool, the ratio + /// can become skewed due to some slashed ratio getting merged in at some point. + type WithEraWithdrawWindow: Get; } /// Active delegators. @@ -457,7 +468,7 @@ pub mod pallet { let new_points = bonded_pool.points_to_issue(exact_amount_to_bond); bonded_pool.points = bonded_pool.points.saturating_add(new_points); - T::StakingInterface::bond_extra(&bonded_pool.account_id, exact_amount_to_bond)?; + T::StakingInterface::bond_extra(bonded_pool.account_id.clone(), exact_amount_to_bond)?; DelegatorStorage::insert( who.clone(), @@ -526,7 +537,7 @@ pub mod pallet { DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPoolsStorage::::get(delegator.pool).unwrap_or_default(); - let current_era = T::StakingInterface::current_era(); + let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); let balance_to_unbond = bonded_pool.balance_to_unbond(delegator.points); @@ -536,7 +547,7 @@ pub mod pallet { // TODO: call withdraw unbonded to try and minimize unbonding chunks // Unbond in the actual underlying pool - T::StakingInterface::unbond(&bonded_pool.account_id, balance_to_unbond)?; + T::StakingInterface::unbond(bonded_pool.account_id.clone(), balance_to_unbond)?; // Merge any older pools into the general, era agnostic unbond pool. Note that we do // this before inserting to ensure we don't go over the max unbonding pools. @@ -576,7 +587,7 @@ pub mod pallet { DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; - let current_era = T::StakingInterface::current_era(); + let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); if current_era.saturating_sub(unbonding_era) < T::StakingInterface::bonding_duration() { return Err(Error::::NotUnbondedYet.into()) }; @@ -605,7 +616,10 @@ pub mod pallet { BondedPoolStorage::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; if T::Currency::free_balance(&bonded_pool.account_id) < balance_to_unbond { - T::StakingInterface::withdraw_unbonded(&bonded_pool.account_id)?; + T::StakingInterface::withdraw_unbonded( + bonded_pool.account_id.clone(), + &bonded_pool.account_id, + )?; } T::Currency::transfer( @@ -645,13 +659,12 @@ pub mod pallet { ensure!(!BondedPoolStorage::::contains_key(id), Error::::IdInUse); ensure!(amount >= T::StakingInterface::minimum_bond(), Error::::MinimumBondNotMet); - // TODO create can_* fns so we can bail in the beggining if some pre-conditions are not let (stash, reward_dest) = Self::create_accounts(id); ensure!( T::StakingInterface::can_nominate(&stash, &targets) && - T::StakingInterface::can_bond(&stash, &stash, &reward_dest), + T::StakingInterface::can_bond(&stash, &stash, amount, &reward_dest), Error::::StakingError ); @@ -710,7 +723,7 @@ pub mod pallet { impl Hooks> for Pallet { fn integrity_test() { assert!( - T::StakingInterface::bonding_duration() < T::MaxUnbonding::get(), + T::StakingInterface::bonding_duration() < MaxUnbonding::::get(), "There must be more unbonding pools then the bonding duration / so a slash can be applied to relevant unboding pools. (We assume / the bonding duration > slash deffer duration.", diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 7963c87dad88d..9f76e794ff64d 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -36,8 +36,8 @@ impl sp_staking::StakingInterface for StakingMock { 10 } - fn current_era() -> EraIndex { - CurrentEra::get() + fn current_era() -> Option { + Some(CurrentEra::get()) } fn bonding_duration() -> EraIndex { @@ -52,37 +52,45 @@ impl sp_staking::StakingInterface for StakingMock { CanBondExtra::get() } - fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { - BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(who).unwrap() += extra); + fn bond_extra(who: Self::AccountId, extra: Self::Balance) -> DispatchResult { + BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(&who).unwrap() += extra); Ok(()) } - fn unbond(who: &Self::AccountId, amount: Self::Balance) -> DispatchResult { - BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(who).unwrap() -= amount); + fn unbond(who: Self::AccountId, amount: Self::Balance) -> DispatchResult { + BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(&who).unwrap() -= amount); UNBONDING_BALANCE_MAP - .with(|m| *m.borrow_mut().entry(*who).or_insert(Self::Balance::zero()) += amount); + .with(|m| *m.borrow_mut().entry(who).or_insert(Self::Balance::zero()) += amount); Ok(()) } - fn withdraw_unbonded(who: &Self::AccountId) -> DispatchResult { - let maybe_new_free = UNBONDING_BALANCE_MAP.with(|m| m.borrow_mut().remove(who)); + fn withdraw_unbonded( + who: Self::AccountId, + stash: &Self::AccountId, + ) -> Result { + let maybe_new_free = UNBONDING_BALANCE_MAP.with(|m| m.borrow_mut().remove(&who)); if let Some(new_free) = maybe_new_free { - assert_ok!(Balances::mutate_account(who, |a| a.free += new_free)); + assert_ok!(Balances::mutate_account(&who, |a| a.free += new_free)); } - Ok(()) + Ok(100) } - fn can_bond(_: &Self::AccountId, _: &Self::AccountId, _: &Self::AccountId) -> bool { + fn can_bond( + _: &Self::AccountId, + _: &Self::AccountId, + _: Self::Balance, + _: &Self::AccountId, + ) -> bool { CanBond::get() } fn bond( stash: Self::AccountId, _: Self::AccountId, - amount: Self::Balance, + value: Self::Balance, _: Self::AccountId, ) -> DispatchResult { - StakingMock::set_bonded_balance(stash, amount); + StakingMock::set_bonded_balance(stash, value); Ok(()) } @@ -153,7 +161,7 @@ impl Convert for U256ToBalance { } parameter_types! { - pub static MaxUnbonding: u32 = 5; + pub static WithEraWithdrawWindow: u32 = 2; } impl pools::Config for Runtime { @@ -162,7 +170,7 @@ impl pools::Config for Runtime { type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; type StakingInterface = StakingMock; - type MaxUnbonding = MaxUnbonding; + type WithEraWithdrawWindow = WithEraWithdrawWindow; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 5b5d1413f7170..a8cf5e4e11949 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -6,8 +6,10 @@ use crate::mock::{ }; use frame_support::{assert_noop, assert_ok}; -// +// +// - get pallet-pools to compile and pass test // - implement staking impl of the delegator pools interface +// - factor out can_* -> pre_execution_checks // - test `slash_pool` // - incorporate returned weight from staking calls @@ -282,7 +284,7 @@ mod sub_pools { #[test] fn maybe_merge_pools_works() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(::MaxUnbonding::get(), 5); + assert_eq!(MaxUnbonding::::get(), 5); // Given let mut sub_pool_0 = SubPools:: { @@ -1194,7 +1196,7 @@ mod unbond { BondedPool { account_id: PRIMARY_ACCOUNT, points: 0 } ); - assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 0); + assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); }); } @@ -1220,7 +1222,7 @@ mod unbond { BondedPoolStorage::::get(0).unwrap(), BondedPool { account_id: PRIMARY_ACCOUNT, points: 560 } ); - assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 94); + assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 94); assert_eq!(DelegatorStorage::::get(40).unwrap().unbonding_era, Some(0)); assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding @@ -1236,7 +1238,7 @@ mod unbond { BondedPoolStorage::::get(0).unwrap(), BondedPool { account_id: PRIMARY_ACCOUNT, points: 550 } ); - assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 93); + assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 93); assert_eq!(DelegatorStorage::::get(10).unwrap().unbonding_era, Some(0)); assert_eq!(Balances::free_balance(&10), 10 + 10); @@ -1252,7 +1254,7 @@ mod unbond { BondedPoolStorage::::get(0).unwrap(), BondedPool { account_id: PRIMARY_ACCOUNT, points: 0 } ); - assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), 0); + assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); assert_eq!(DelegatorStorage::::get(550).unwrap().unbonding_era, Some(0)); assert_eq!(Balances::free_balance(&550), 550 + 550); }); @@ -1275,7 +1277,7 @@ mod unbond { ); // When - let current_era = 1 + ::MaxUnbonding::get(); + let current_era = 1 + MaxUnbonding::::get(); CurrentEra::set(current_era); assert_ok!(Pools::unbond(Origin::signed(10))); @@ -1351,7 +1353,7 @@ mod withdraw_unbonded { // Advance the current_era to ensure all `with_era` pools will be merged into // `no_era` pool - current_era += ::MaxUnbonding::get(); + current_era += MaxUnbonding::::get(); CurrentEra::set(current_era); // Simulate some other call to unbond that would merge `with_era` pools into @@ -1536,7 +1538,7 @@ mod create { assert!(!BondedPoolStorage::::contains_key(1)); assert!(!RewardPoolStorage::::contains_key(1)); assert!(!DelegatorStorage::::contains_key(11)); - assert_eq!(StakingMock::bonded_balance(&bonded_account), 0); + assert_eq!(StakingMock::bonded_balance(&bonded_account), None); Balances::make_free_balance_be(&11, StakingMock::minimum_bond()); assert_ok!(Pools::create(Origin::signed(11), 1, vec![], StakingMock::minimum_bond())); @@ -1555,7 +1557,10 @@ mod create { BondedPoolStorage::::get(1).unwrap(), BondedPool { points: StakingMock::minimum_bond(), account_id: bonded_account } ); - assert_eq!(StakingMock::bonded_balance(&bonded_account), StakingMock::minimum_bond()); + assert_eq!( + StakingMock::bonded_balance(&bonded_account).unwrap(), + StakingMock::minimum_bond() + ); assert_eq!( RewardPoolStorage::::get(1).unwrap(), RewardPool { diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 625ff327dad8e..09980dc8e5547 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -788,9 +788,9 @@ pub(crate) fn on_offence_in_era( for &(bonded_era, start_session) in bonded_eras.iter() { if bonded_era == era { let _ = Staking::on_offence(offenders, slash_fraction, start_session, disable_strategy); - return; + return } else if bonded_era > era { - break; + break } } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 78549369eab24..67dfdbb179026 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -143,7 +143,7 @@ impl Pallet { // Nothing to do if they have no reward points. if validator_reward_points.is_zero() { - return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into()); + return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into()) } // This is the fraction of the total reward that the validator and the @@ -235,9 +235,8 @@ impl Pallet { Self::update_ledger(&controller, &l); r }), - RewardDestination::Account(dest_account) => { - Some(T::Currency::deposit_creating(&dest_account, amount)) - }, + RewardDestination::Account(dest_account) => + Some(T::Currency::deposit_creating(&dest_account, amount)), RewardDestination::None => None, } } @@ -265,14 +264,14 @@ impl Pallet { _ => { // Either `Forcing::ForceNone`, // or `Forcing::NotForcing if era_length >= T::SessionsPerEra::get()`. - return None; + return None }, } // New era. let maybe_new_era_validators = Self::try_trigger_new_era(session_index, is_genesis); - if maybe_new_era_validators.is_some() - && matches!(ForceEra::::get(), Forcing::ForceNew) + if maybe_new_era_validators.is_some() && + matches!(ForceEra::::get(), Forcing::ForceNew) { ForceEra::::put(Forcing::NotForcing); } @@ -463,7 +462,7 @@ impl Pallet { } Self::deposit_event(Event::StakingElectionFailed); - return None; + return None } Self::deposit_event(Event::StakersElected); @@ -870,7 +869,7 @@ impl ElectionDataProvider for Pallet { // We can't handle this case yet -- return an error. if maybe_max_len.map_or(false, |max_len| target_count > max_len as u32) { - return Err("Target snapshot too big"); + return Err("Target snapshot too big") } Ok(Self::get_npos_targets()) @@ -1139,7 +1138,7 @@ where add_db_reads_writes(1, 0); if active_era.is_none() { // This offence need not be re-submitted. - return consumed_weight; + return consumed_weight } active_era.expect("value checked not to be `None`; qed").index }; @@ -1185,7 +1184,7 @@ where // Skip if the validator is invulnerable. if invulnerables.contains(stash) { - continue; + continue } let unapplied = slashing::compute_slash::(slashing::SlashParams { @@ -1355,6 +1354,7 @@ impl StakingInterface for Pallet { fn withdraw_unbonded( controller: Self::AccountId, stash: &Self::AccountId, + // TODO: make this num slashing spans ) -> Result { // TODO should probably just make this an input param let num_slashing_spans = match as Store>::SlashingSpans::get(stash) { @@ -1378,19 +1378,19 @@ impl StakingInterface for Pallet { _: &Self::AccountId, ) -> bool { if Bonded::::contains_key(stash) { - return false; + return false } if Ledger::::contains_key(controller) { - return false; + return false } if value < T::Currency::minimum_balance() { - return false; + return false } if !frame_system::Pallet::::can_inc_consumer(stash) { - return false; + return false } true @@ -1418,33 +1418,33 @@ impl StakingInterface for Pallet { let stash = &ledger.stash; if ledger.active < MinNominatorBond::::get() { - return false; + return false } if !Nominators::::contains_key(stash) { if let Some(max_nominators) = MaxNominatorsCount::::get() { if Nominators::::count() > max_nominators { - return false; + return false }; } } if targets.is_empty() { - return false; + return false } if targets.len() as u32 > T::MAX_NOMINATIONS { - return false; + return false } let old = Nominators::::get(stash).map_or_else(Vec::new, |x| x.targets); for validator in targets { if let Ok(validator) = T::Lookup::lookup(validator.clone()) { if !(old.contains(&validator) || !Validators::::get(&validator).blocked) { - return false; + return false } } else { - return false; + return false } } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 7451321719624..bb6c916689bb8 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -275,9 +275,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 - + part_for_100_from_10 * total_payout_0 * 2 / 3 - + part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * total_payout_0 * 2 / 3 + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -313,9 +313,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 - + part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) - + part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -3967,8 +3967,8 @@ mod election_data_provider { #[test] fn targets_2sec_block() { let mut validators = 1000; - while ::WeightInfo::get_npos_targets(validators) - < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_targets(validators) < + 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { validators += 1; } @@ -3985,8 +3985,8 @@ mod election_data_provider { let slashing_spans = validators; let mut nominators = 1000; - while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) - < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) < + 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { nominators += 1; } @@ -4057,8 +4057,8 @@ mod election_data_provider { .build_and_execute(|| { // sum of all nominators who'd be voters (1), plus the self-votes (4). assert_eq!( - ::SortedListProvider::count() - + >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 5 ); @@ -4097,8 +4097,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() - + >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 7 ); @@ -4142,8 +4142,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() - + >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 6 ); diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index b106eee34d3e0..957ecc4462518 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -78,6 +78,8 @@ pub trait StakingInterface { fn bonding_duration() -> EraIndex; /// The current era for the staking system. + // TODO: Is it ok to assume None is always era zero? If so, then in the impl we ca do + // unwrap_or(0) fn current_era() -> Option; /// Balance `controller` has bonded for nominating. From 9a70a8335734e9e3939265fdb1c7741c16196762 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 27 Jan 2022 10:17:19 -0800 Subject: [PATCH 046/299] Use num_slashing_spans in Pools::withdraw_unbonded --- frame/pools/src/lib.rs | 4 ++-- frame/pools/src/mock.rs | 5 +---- frame/pools/src/tests.rs | 26 +++++++++++++------------- frame/staking/src/pallet/impls.rs | 9 +-------- primitives/staking/src/lib.rs | 2 +- 5 files changed, 18 insertions(+), 28 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 40dbc7f0583de..67785c6d6541b 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -581,7 +581,7 @@ pub mod pallet { } #[pallet::weight(666)] - pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResult { + pub fn withdraw_unbonded(origin: OriginFor, num_slashing_spans: u32) -> DispatchResult { let who = ensure_signed(origin)?; let delegator = DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; @@ -618,7 +618,7 @@ pub mod pallet { if T::Currency::free_balance(&bonded_pool.account_id) < balance_to_unbond { T::StakingInterface::withdraw_unbonded( bonded_pool.account_id.clone(), - &bonded_pool.account_id, + num_slashing_spans, )?; } diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 9f76e794ff64d..322f4519caa01 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -64,10 +64,7 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } - fn withdraw_unbonded( - who: Self::AccountId, - stash: &Self::AccountId, - ) -> Result { + fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result { let maybe_new_free = UNBONDING_BALANCE_MAP.with(|m| m.borrow_mut().remove(&who)); if let Some(new_free) = maybe_new_free { assert_ok!(Balances::mutate_account(&who, |a| a.free += new_free)); diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index a8cf5e4e11949..957a326e47916 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -6,7 +6,7 @@ use crate::mock::{ }; use frame_support::{assert_noop, assert_ok}; -// +// // - get pallet-pools to compile and pass test // - implement staking impl of the delegator pools interface // - factor out can_* -> pre_execution_checks @@ -1370,7 +1370,7 @@ mod withdraw_unbonded { SubPoolsStorage::::insert(0, sub_pools); // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(550))); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 0)); // Then assert_eq!( @@ -1382,7 +1382,7 @@ mod withdraw_unbonded { assert!(!DelegatorStorage::::contains_key(550)); // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(40))); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 0)); // Then assert_eq!( @@ -1394,7 +1394,7 @@ mod withdraw_unbonded { assert!(!DelegatorStorage::::contains_key(40)); // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(10))); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 0)); // Then assert_eq!( @@ -1428,7 +1428,7 @@ mod withdraw_unbonded { CurrentEra::set(StakingMock::bonding_duration()); // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(40))); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 0)); // Then assert_eq!( @@ -1440,7 +1440,7 @@ mod withdraw_unbonded { assert!(!DelegatorStorage::::contains_key(40)); // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(10))); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 0)); // Then assert_eq!( @@ -1452,7 +1452,7 @@ mod withdraw_unbonded { assert!(!DelegatorStorage::::contains_key(10)); // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(550))); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 0)); // Then assert_eq!( @@ -1469,7 +1469,7 @@ mod withdraw_unbonded { fn withdraw_unbonded_errors_correctly() { ExtBuilder::default().build_and_execute(|| { assert_noop!( - Pools::withdraw_unbonded(Origin::signed(11)), + Pools::withdraw_unbonded(Origin::signed(11), 0), Error::::DelegatorNotFound ); @@ -1482,7 +1482,7 @@ mod withdraw_unbonded { DelegatorStorage::::insert(11, delegator.clone()); assert_noop!( - Pools::withdraw_unbonded(Origin::signed(11)), + Pools::withdraw_unbonded(Origin::signed(11), 0), Error::::NotUnbonding ); @@ -1490,14 +1490,14 @@ mod withdraw_unbonded { DelegatorStorage::::insert(11, delegator.clone()); assert_noop!( - Pools::withdraw_unbonded(Origin::signed(11)), + Pools::withdraw_unbonded(Origin::signed(11), 0), Error::::NotUnbondedYet ); CurrentEra::set(StakingMock::bonding_duration()); assert_noop!( - Pools::withdraw_unbonded(Origin::signed(11)), + Pools::withdraw_unbonded(Origin::signed(11), 0), Error::::SubPoolsNotFound ); @@ -1508,14 +1508,14 @@ mod withdraw_unbonded { SubPoolsStorage::::insert(1, sub_pools.clone()); assert_noop!( - Pools::withdraw_unbonded(Origin::signed(11)), + Pools::withdraw_unbonded(Origin::signed(11), 0), Error::::PoolNotFound ); BondedPoolStorage::::insert(1, BondedPool { points: 0, account_id: 123 }); assert_eq!(Balances::free_balance(&123), 0); assert_noop!( - Pools::withdraw_unbonded(Origin::signed(11)), + Pools::withdraw_unbonded(Origin::signed(11), 0), pallet_balances::Error::::InsufficientBalance ); diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 67dfdbb179026..7b30bc56504a1 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1353,15 +1353,8 @@ impl StakingInterface for Pallet { fn withdraw_unbonded( controller: Self::AccountId, - stash: &Self::AccountId, - // TODO: make this num slashing spans + num_slashing_spans: u32, ) -> Result { - // TODO should probably just make this an input param - let num_slashing_spans = match as Store>::SlashingSpans::get(stash) { - None => 0, - Some(s) => s.iter().count() as u32, - }; - Self::withdraw_unbonded(RawOrigin::Signed(controller).into(), num_slashing_spans) .map(|post_info| { post_info diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 957ecc4462518..e789ea354d2f6 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -95,7 +95,7 @@ pub trait StakingInterface { fn withdraw_unbonded( controller: Self::AccountId, - stash: &Self::AccountId, + num_slashing_spans: u32, ) -> Result; /// Check if the given accounts can be bonded as stash <-> controller pair and with the given From 012b390d99189f19f142adb4677fd5159809a97d Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 27 Jan 2022 10:53:23 -0800 Subject: [PATCH 047/299] Factor out part of bond to bond_checks --- frame/staking/src/pallet/impls.rs | 41 ++++++++++++++++++------------- frame/staking/src/pallet/mod.rs | 26 ++------------------ frame/staking/src/tests.rs | 6 ++++- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 7b30bc56504a1..fce4261d0a1dc 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -842,6 +842,28 @@ impl Pallet { DispatchClass::Mandatory, ); } + + // Checks for [`Self::bond`] that can be completed at the beginning of the calls logic. + pub(crate) fn bond_checks( + stash: &T::AccountId, + controller: &T::AccountId, + value: BalanceOf, + ) -> Result<(), DispatchError> { + if Bonded::::contains_key(&stash) { + Err(Error::::AlreadyBonded)? + } + + if Ledger::::contains_key(&controller) { + Err(Error::::AlreadyPaired)? + } + + // Reject a bond which is considered to be _dust_. + if value < T::Currency::minimum_balance() { + Err(Error::::InsufficientBond)? + } + + Ok(()) + } } impl ElectionDataProvider for Pallet { @@ -1370,23 +1392,8 @@ impl StakingInterface for Pallet { value: Self::Balance, _: &Self::AccountId, ) -> bool { - if Bonded::::contains_key(stash) { - return false - } - - if Ledger::::contains_key(controller) { - return false - } - - if value < T::Currency::minimum_balance() { - return false - } - - if !frame_system::Pallet::::can_inc_consumer(stash) { - return false - } - - true + Self::bond_checks(stash, controller, value).is_ok() && + frame_system::Pallet::::can_inc_consumer(stash) } fn bond( diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 1cf6b56152a98..845ea3456b5eb 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -739,21 +739,9 @@ pub mod pallet { payee: RewardDestination, ) -> DispatchResult { let stash = ensure_signed(origin)?; - - if >::contains_key(&stash) { - Err(Error::::AlreadyBonded)? - } - let controller = T::Lookup::lookup(controller)?; - if >::contains_key(&controller) { - Err(Error::::AlreadyPaired)? - } - - // Reject a bond which is considered to be _dust_. - if value < T::Currency::minimum_balance() { - Err(Error::::InsufficientBond)? - } + Self::bond_checks(&stash, &controller, value)?; frame_system::Pallet::::inc_consumers(&stash).map_err(|_| Error::::BadState)?; @@ -882,17 +870,7 @@ pub mod pallet { // Note: in case there is no current era it is fine to bond one era more. let era = Self::current_era().unwrap_or(0) + T::BondingDuration::get(); - if let Some(mut chunk) = ledger.unlocking.last_mut() { - if chunk.era == era { - // To keep the chunk count down, we only keep one chunk per era. Since - // unlocking is a queue, if a chunk exists for the era we know that it would - // be the last one - use sp_runtime::traits::Saturating; - chunk.value = chunk.value.saturating_add(value) - } - } else { - ledger.unlocking.push(UnlockChunk { value, era }); - }; + ledger.unlocking.push(UnlockChunk { value, era }); // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index bb6c916689bb8..90d117037dc7e 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4720,7 +4720,11 @@ mod staking_interface { assert_eq!(Staking::can_bond(&81, &80, 100, &0), false); // Works with valid inputs - assert!(Staking::can_bond(&101, &100, 100, &0)) + assert!(Staking::can_bond(&101, &100, 100, &0)); + + // Clean up so post checks work + Ledger::::remove(60); + Bonded::::remove(71); }); } From 24a3cff457a7a6d5328a8a4a7f9089c438f11f2b Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 27 Jan 2022 12:17:14 -0800 Subject: [PATCH 048/299] Factor out part of nominate to nominate_checks --- frame/staking/src/pallet/impls.rs | 106 ++++++++++++++++-------------- frame/staking/src/pallet/mod.rs | 83 +++++++---------------- frame/staking/src/tests.rs | 62 +++++++++-------- primitives/staking/src/lib.rs | 2 +- 4 files changed, 112 insertions(+), 141 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index fce4261d0a1dc..c530e32b8a8bc 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -143,7 +143,7 @@ impl Pallet { // Nothing to do if they have no reward points. if validator_reward_points.is_zero() { - return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into()) + return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into()); } // This is the fraction of the total reward that the validator and the @@ -235,8 +235,9 @@ impl Pallet { Self::update_ledger(&controller, &l); r }), - RewardDestination::Account(dest_account) => - Some(T::Currency::deposit_creating(&dest_account, amount)), + RewardDestination::Account(dest_account) => { + Some(T::Currency::deposit_creating(&dest_account, amount)) + }, RewardDestination::None => None, } } @@ -264,14 +265,14 @@ impl Pallet { _ => { // Either `Forcing::ForceNone`, // or `Forcing::NotForcing if era_length >= T::SessionsPerEra::get()`. - return None + return None; }, } // New era. let maybe_new_era_validators = Self::try_trigger_new_era(session_index, is_genesis); - if maybe_new_era_validators.is_some() && - matches!(ForceEra::::get(), Forcing::ForceNew) + if maybe_new_era_validators.is_some() + && matches!(ForceEra::::get(), Forcing::ForceNew) { ForceEra::::put(Forcing::NotForcing); } @@ -462,7 +463,7 @@ impl Pallet { } Self::deposit_event(Event::StakingElectionFailed); - return None + return None; } Self::deposit_event(Event::StakersElected); @@ -864,6 +865,46 @@ impl Pallet { Ok(()) } + + pub(crate) fn nominate_checks( + controller: &T::AccountId, + targets: Vec<::Source>, + ) -> Result<(T::AccountId, Vec), DispatchError> { + let ledger = Self::ledger(controller).ok_or(Error::::NotController)?; + ensure!(ledger.active >= MinNominatorBond::::get(), Error::::InsufficientBond); + + // Only check limits if they are not already a nominator. + if !Nominators::::contains_key(&ledger.stash) { + // If this error is reached, we need to adjust the `MinNominatorBond` and start + // calling `chill_other`. Until then, we explicitly block new nominators to protect + // the runtime. + if let Some(max_nominators) = MaxNominatorsCount::::get() { + ensure!(Nominators::::count() < max_nominators, Error::::TooManyNominators); + } + } + + ensure!(!targets.is_empty(), Error::::EmptyTargets); + ensure!(targets.len() <= T::MAX_NOMINATIONS as usize, Error::::TooManyTargets); + + let old = Nominators::::get(&ledger.stash).map_or_else(Vec::new, |x| x.targets); + + let targets = targets + .into_iter() + .map(|t| T::Lookup::lookup(t).map_err(DispatchError::from)) + .map(|n| { + n.and_then(|n| { + println!("old: {:?}, n: {:?}, old.contains(n): {:?}", old, n, old.contains(&n)); + if old.contains(&n) || !Validators::::get(&n).blocked { + Ok(n) + } else { + Err(Error::::BadTarget.into()) + } + }) + }) + .collect::, _>>()?; + + Ok((ledger.stash, targets)) + } } impl ElectionDataProvider for Pallet { @@ -891,7 +932,7 @@ impl ElectionDataProvider for Pallet { // We can't handle this case yet -- return an error. if maybe_max_len.map_or(false, |max_len| target_count > max_len as u32) { - return Err("Target snapshot too big") + return Err("Target snapshot too big"); } Ok(Self::get_npos_targets()) @@ -1160,7 +1201,7 @@ where add_db_reads_writes(1, 0); if active_era.is_none() { // This offence need not be re-submitted. - return consumed_weight + return consumed_weight; } active_era.expect("value checked not to be `None`; qed").index }; @@ -1206,7 +1247,7 @@ where // Skip if the validator is invulnerable. if invulnerables.contains(stash) { - continue + continue; } let unapplied = slashing::compute_slash::(slashing::SlashParams { @@ -1392,8 +1433,8 @@ impl StakingInterface for Pallet { value: Self::Balance, _: &Self::AccountId, ) -> bool { - Self::bond_checks(stash, controller, value).is_ok() && - frame_system::Pallet::::can_inc_consumer(stash) + Self::bond_checks(stash, controller, value).is_ok() + && frame_system::Pallet::::can_inc_consumer(stash) } fn bond( @@ -1410,45 +1451,8 @@ impl StakingInterface for Pallet { ) } - fn can_nominate(controller: &Self::AccountId, targets: &Vec) -> bool { - let ledger = match Self::ledger(&controller) { - Some(l) => l, - None => return false, - }; - let stash = &ledger.stash; - - if ledger.active < MinNominatorBond::::get() { - return false - } - - if !Nominators::::contains_key(stash) { - if let Some(max_nominators) = MaxNominatorsCount::::get() { - if Nominators::::count() > max_nominators { - return false - }; - } - } - - if targets.is_empty() { - return false - } - - if targets.len() as u32 > T::MAX_NOMINATIONS { - return false - } - - let old = Nominators::::get(stash).map_or_else(Vec::new, |x| x.targets); - for validator in targets { - if let Ok(validator) = T::Lookup::lookup(validator.clone()) { - if !(old.contains(&validator) || !Validators::::get(&validator).blocked) { - return false - } - } else { - return false - } - } - - true + fn can_nominate(controller: &Self::AccountId, targets: Vec) -> bool { + Self::nominate_checks(controller, targets).is_ok() } fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult { diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 845ea3456b5eb..d091021e2b935 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -29,10 +29,10 @@ use frame_support::{ use frame_system::{ensure_root, ensure_signed, offchain::SendTransactionTypes, pallet_prelude::*}; use sp_runtime::{ traits::{CheckedSub, SaturatedConversion, StaticLookup, Zero}, - DispatchError, Perbill, Percent, + Perbill, Percent, }; use sp_staking::{EraIndex, PoolsInterface, SessionIndex}; -use sp_std::{convert::From, prelude::*, result}; +use sp_std::{convert::From, prelude::*}; mod impls; @@ -911,24 +911,23 @@ pub mod pallet { ledger = ledger.consolidate_unlocked(current_era) } - let post_info_weight = if ledger.unlocking.is_empty() && - ledger.active < T::Currency::minimum_balance() - { - // This account must have called `unbond()` with some value that caused the active - // portion to fall below existential deposit + will have no more unlocking chunks - // left. We can now safely remove all staking-related information. - Self::kill_stash(&stash, num_slashing_spans)?; - // Remove the lock. - T::Currency::remove_lock(STAKING_ID, &stash); - // This is worst case scenario, so we use the full weight and return None - None - } else { - // This was the consequence of a partial unbond. just update the ledger and move on. - Self::update_ledger(&controller, &ledger); + let post_info_weight = + if ledger.unlocking.is_empty() && ledger.active < T::Currency::minimum_balance() { + // This account must have called `unbond()` with some value that caused the active + // portion to fall below existential deposit + will have no more unlocking chunks + // left. We can now safely remove all staking-related information. + Self::kill_stash(&stash, num_slashing_spans)?; + // Remove the lock. + T::Currency::remove_lock(STAKING_ID, &stash); + // This is worst case scenario, so we use the full weight and return None + None + } else { + // This was the consequence of a partial unbond. just update the ledger and move on. + Self::update_ledger(&controller, &ledger); - // This is only an update, so we use less overall weight. - Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) - }; + // This is only an update, so we use less overall weight. + Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) + }; // `old_total` should never be less than the new total because // `consolidate_unlocked` strictly subtracts balance. @@ -993,43 +992,7 @@ pub mod pallet { targets: Vec<::Source>, ) -> DispatchResult { let controller = ensure_signed(origin)?; - - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; - ensure!(ledger.active >= MinNominatorBond::::get(), Error::::InsufficientBond); - let stash = &ledger.stash; - - // Only check limits if they are not already a nominator. - if !Nominators::::contains_key(stash) { - // If this error is reached, we need to adjust the `MinNominatorBond` and start - // calling `chill_other`. Until then, we explicitly block new nominators to protect - // the runtime. - if let Some(max_nominators) = MaxNominatorsCount::::get() { - ensure!( - Nominators::::count() < max_nominators, - Error::::TooManyNominators - ); - } - } - - ensure!(!targets.is_empty(), Error::::EmptyTargets); - ensure!(targets.len() <= T::MAX_NOMINATIONS as usize, Error::::TooManyTargets); - - let old = Nominators::::get(stash).map_or_else(Vec::new, |x| x.targets); - - let targets = targets - .into_iter() - .map(|t| T::Lookup::lookup(t).map_err(DispatchError::from)) - .map(|n| { - n.and_then(|n| { - if old.contains(&n) || !Validators::::get(&n).blocked { - Ok(n) - } else { - Err(Error::::BadTarget.into()) - } - }) - }) - .collect::, _>>()?; - + let (stash, targets) = Self::nominate_checks(&controller, targets)?; let nominations = Nominations { targets, // Initial nominations are considered submitted at era 0. See `Nominations` doc @@ -1037,8 +1000,8 @@ pub mod pallet { suppressed: false, }; - Self::do_remove_validator(stash); - Self::do_add_nominator(stash, nominations); + Self::do_remove_validator(&stash); + Self::do_add_nominator(&stash, nominations); Ok(()) } @@ -1454,8 +1417,8 @@ pub mod pallet { let _ = ensure_signed(origin)?; let ed = T::Currency::minimum_balance(); - let reapable = T::Currency::total_balance(&stash) < ed || - Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) + let reapable = T::Currency::total_balance(&stash) < ed + || Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) .map(|l| l.total) .unwrap_or_default() < ed; ensure!(reapable, Error::::FundedTarget); diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 90d117037dc7e..d0dda99159f56 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -275,9 +275,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 + - part_for_100_from_10 * total_payout_0 * 2 / 3 + - part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * total_payout_0 * 2 / 3 + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -313,9 +313,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 + - part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) + - part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -3967,8 +3967,8 @@ mod election_data_provider { #[test] fn targets_2sec_block() { let mut validators = 1000; - while ::WeightInfo::get_npos_targets(validators) < - 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_targets(validators) + < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { validators += 1; } @@ -3985,8 +3985,8 @@ mod election_data_provider { let slashing_spans = validators; let mut nominators = 1000; - while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) < - 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) + < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { nominators += 1; } @@ -4057,8 +4057,8 @@ mod election_data_provider { .build_and_execute(|| { // sum of all nominators who'd be voters (1), plus the self-votes (4). assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 5 ); @@ -4097,8 +4097,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 7 ); @@ -4142,8 +4142,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 6 ); @@ -4621,27 +4621,31 @@ mod staking_interface { #[test] fn can_nominate_passes_valid_inputs() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(Staking::bond(Origin::signed(80), 81, 1, RewardDestination::Controller)); + assert_ok!(Staking::bond(Origin::signed(81), 80, 1, RewardDestination::Controller)); let targets = vec![11]; - assert!(Staking::can_nominate(&81, &targets)); + // First time nominating + assert!(Staking::can_nominate(&80, targets.clone())); + + // Now actually nominate + assert_ok!(Staking::nominate(Origin::signed(80), targets.clone())); // 11 blocks assert_ok!(Staking::validate( Origin::signed(10), ValidatorPrefs { blocked: true, ..Default::default() } )); - // but we can still nominate them since they are in the old nominations - assert!(Staking::can_nominate(&81, &targets)); + // but we can still re-nominate them since they are in the old nominations + assert!(Staking::can_nominate(&80, targets.clone())); // The nominator count limit is set to 0 MaxNominatorsCount::::set(Some(0)); // but we can still nominate because we have pre-existing nomination - assert!(Staking::can_nominate(&81, &targets)); + assert!(Staking::can_nominate(&80, targets.clone())); - // They can still nominate with exactly `MAX_NOMINATIONS + // They can nominate with exactly `MAX_NOMINATIONS` targets let targets: Vec<_> = (0..Test::MAX_NOMINATIONS).map(|i| i as u64).collect(); - assert!(Staking::can_nominate(&81, &targets)); + assert!(Staking::can_nominate(&80, targets.clone())); }); } @@ -4655,7 +4659,7 @@ mod staking_interface { let targets = vec![11]; // Not bonded, so no ledger - assert_eq!(Staking::can_nominate(&80, &targets), false); + assert_eq!(Staking::can_nominate(&80, targets.clone()), false); // Bonded, but below min bond to nominate assert_ok!(Staking::bond( @@ -4664,7 +4668,7 @@ mod staking_interface { 1_000 - 10, RewardDestination::Controller )); - assert_eq!(Staking::can_nominate(&80, &targets), false); + assert_eq!(Staking::can_nominate(&80, targets.clone()), false); // Meets min bond, but already at nominator limit assert_ok!(Staking::bond( @@ -4674,16 +4678,16 @@ mod staking_interface { RewardDestination::Controller )); MaxNominatorsCount::::set(Some(0)); - assert_eq!(Staking::can_nominate(&70, &targets), false); + assert_eq!(Staking::can_nominate(&70, targets.clone()), false); MaxNominatorsCount::::set(None); // Targets are empty let targets = vec![]; - assert_eq!(Staking::can_nominate(&70, &targets), false); + assert_eq!(Staking::can_nominate(&70, targets.clone()), false); // Too many targets let targets: Vec<_> = (0..=Test::MAX_NOMINATIONS).map(|i| i as u64).collect(); - assert_eq!(Staking::can_nominate(&70, &targets), false); + assert_eq!(Staking::can_nominate(&70, targets.clone()), false); // A target is blocking assert_ok!(Staking::validate( @@ -4691,7 +4695,7 @@ mod staking_interface { ValidatorPrefs { blocked: true, ..Default::default() } )); let targets = vec![11]; - assert_eq!(Staking::can_nominate(&70, &targets), false); + assert_eq!(Staking::can_nominate(&70, targets.clone()), false); }); } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index e789ea354d2f6..b2fba9b23b825 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -117,7 +117,7 @@ pub trait StakingInterface { /// Check if the given account can nominate. Assumes the account will be correctly bonded after /// this call. - fn can_nominate(controller: &Self::AccountId, targets: &Vec) -> bool; + fn can_nominate(controller: &Self::AccountId, targets: Vec) -> bool; fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult; } From 9559118939876fbb2273e978642cbf7215d82c0d Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 27 Jan 2022 13:25:41 -0800 Subject: [PATCH 049/299] WIP --- frame/staking/src/pallet/impls.rs | 40 ++++++++++++++++------- frame/staking/src/pallet/mod.rs | 14 ++------ frame/staking/src/tests.rs | 54 ++++++++++++++++++++----------- primitives/staking/src/lib.rs | 13 +++++--- 4 files changed, 76 insertions(+), 45 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index c530e32b8a8bc..004f9767d8b83 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -845,7 +845,7 @@ impl Pallet { } // Checks for [`Self::bond`] that can be completed at the beginning of the calls logic. - pub(crate) fn bond_checks( + pub(crate) fn do_bond_checks( stash: &T::AccountId, controller: &T::AccountId, value: BalanceOf, @@ -866,7 +866,7 @@ impl Pallet { Ok(()) } - pub(crate) fn nominate_checks( + pub(crate) fn do_nominate_checks( controller: &T::AccountId, targets: Vec<::Source>, ) -> Result<(T::AccountId, Vec), DispatchError> { @@ -893,7 +893,6 @@ impl Pallet { .map(|t| T::Lookup::lookup(t).map_err(DispatchError::from)) .map(|n| { n.and_then(|n| { - println!("old: {:?}, n: {:?}, old.contains(n): {:?}", old, n, old.contains(&n)); if old.contains(&n) || !Validators::::get(&n).blocked { Ok(n) } else { @@ -905,6 +904,18 @@ impl Pallet { Ok((ledger.stash, targets)) } + + pub(crate) fn do_unchecked_nominate_writes(stash: &T::AccountId, targets: Vec) { + let nominations = Nominations { + targets, + // Initial nominations are considered submitted at era 0. See `Nominations` doc + submitted_in: Self::current_era().unwrap_or(0), + suppressed: false, + }; + + Self::do_remove_validator(&stash); + Self::do_add_nominator(&stash, nominations); + } } impl ElectionDataProvider for Pallet { @@ -1427,14 +1438,18 @@ impl StakingInterface for Pallet { .map_err(|err_with_post_info| err_with_post_info.error) } - fn can_bond( + fn bond_checks( stash: &Self::AccountId, controller: &Self::AccountId, value: Self::Balance, _: &Self::AccountId, - ) -> bool { - Self::bond_checks(stash, controller, value).is_ok() - && frame_system::Pallet::::can_inc_consumer(stash) + ) -> Result<(), DispatchError> { + Self::do_bond_checks(stash, controller, value)?; + if frame_system::Pallet::::can_inc_consumer(stash) { + Ok(()) + } else { + Err(Error::::BadState.into()) + } } fn bond( @@ -1451,11 +1466,14 @@ impl StakingInterface for Pallet { ) } - fn can_nominate(controller: &Self::AccountId, targets: Vec) -> bool { - Self::nominate_checks(controller, targets).is_ok() + fn nominate_checks( + controller: &Self::AccountId, + targets: Vec, + ) -> Result<(Self::AccountId, Vec), DispatchError> { + Self::do_nominate_checks(controller, targets) } - fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult { - Self::nominate(RawOrigin::Signed(controller).into(), targets) + fn unchecked_nominate(stash: Self::AccountId, targets: Vec) { + Self::do_unchecked_nominate_writes(&stash, targets); } } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index d091021e2b935..395c18d58c7d3 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -741,7 +741,7 @@ pub mod pallet { let stash = ensure_signed(origin)?; let controller = T::Lookup::lookup(controller)?; - Self::bond_checks(&stash, &controller, value)?; + Self::do_bond_checks(&stash, &controller, value)?; frame_system::Pallet::::inc_consumers(&stash).map_err(|_| Error::::BadState)?; @@ -992,16 +992,8 @@ pub mod pallet { targets: Vec<::Source>, ) -> DispatchResult { let controller = ensure_signed(origin)?; - let (stash, targets) = Self::nominate_checks(&controller, targets)?; - let nominations = Nominations { - targets, - // Initial nominations are considered submitted at era 0. See `Nominations` doc - submitted_in: Self::current_era().unwrap_or(0), - suppressed: false, - }; - - Self::do_remove_validator(&stash); - Self::do_add_nominator(&stash, nominations); + let (stash, targets) = Self::do_nominate_checks(&controller, targets)?; + Self::do_unchecked_nominate_writes(&stash, targets); Ok(()) } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index d0dda99159f56..34eec9c29dff9 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4625,7 +4625,10 @@ mod staking_interface { let targets = vec![11]; // First time nominating - assert!(Staking::can_nominate(&80, targets.clone())); + assert_eq!( + Staking::nominate_checks(&80, targets.clone()).unwrap(), + (81, targets.clone()) + ); // Now actually nominate assert_ok!(Staking::nominate(Origin::signed(80), targets.clone())); @@ -4636,21 +4639,30 @@ mod staking_interface { ValidatorPrefs { blocked: true, ..Default::default() } )); // but we can still re-nominate them since they are in the old nominations - assert!(Staking::can_nominate(&80, targets.clone())); + assert_eq!( + Staking::nominate_checks(&80, targets.clone()).unwrap(), + (81, targets.clone()) + ); // The nominator count limit is set to 0 MaxNominatorsCount::::set(Some(0)); // but we can still nominate because we have pre-existing nomination - assert!(Staking::can_nominate(&80, targets.clone())); + assert_eq!( + Staking::nominate_checks(&80, targets.clone()).unwrap(), + (81, targets.clone()) + ); // They can nominate with exactly `MAX_NOMINATIONS` targets let targets: Vec<_> = (0..Test::MAX_NOMINATIONS).map(|i| i as u64).collect(); - assert!(Staking::can_nominate(&80, targets.clone())); + assert_eq!( + Staking::nominate_checks(&80, targets.clone()).unwrap(), + (81, targets.clone()) + ); }); } #[test] - fn can_nominate_fails_invalid_inputs() { + fn nominate_checks_fails_invalid_inputs() { ExtBuilder::default() .existential_deposit(100) .balance_factor(100) @@ -4659,7 +4671,10 @@ mod staking_interface { let targets = vec![11]; // Not bonded, so no ledger - assert_eq!(Staking::can_nominate(&80, targets.clone()), false); + assert_noop!( + Staking::nominate_checks(&80, targets.clone()), + Error::::AlreadyBonded + ); // Bonded, but below min bond to nominate assert_ok!(Staking::bond( @@ -4668,7 +4683,7 @@ mod staking_interface { 1_000 - 10, RewardDestination::Controller )); - assert_eq!(Staking::can_nominate(&80, targets.clone()), false); + assert_noop!(Staking::nominate_checks(&80, targets.clone()), false); // Meets min bond, but already at nominator limit assert_ok!(Staking::bond( @@ -4678,16 +4693,16 @@ mod staking_interface { RewardDestination::Controller )); MaxNominatorsCount::::set(Some(0)); - assert_eq!(Staking::can_nominate(&70, targets.clone()), false); + assert_eq!(Staking::nominate_checks(&70, targets.clone()), false); MaxNominatorsCount::::set(None); // Targets are empty let targets = vec![]; - assert_eq!(Staking::can_nominate(&70, targets.clone()), false); + assert_eq!(Staking::nominate_checks(&70, targets.clone()), false); // Too many targets let targets: Vec<_> = (0..=Test::MAX_NOMINATIONS).map(|i| i as u64).collect(); - assert_eq!(Staking::can_nominate(&70, targets.clone()), false); + assert_eq!(Staking::nominate_checks(&70, targets.clone()), false); // A target is blocking assert_ok!(Staking::validate( @@ -4695,38 +4710,41 @@ mod staking_interface { ValidatorPrefs { blocked: true, ..Default::default() } )); let targets = vec![11]; - assert_eq!(Staking::can_nominate(&70, targets.clone()), false); + assert_eq!(Staking::nominate_checks(&70, targets.clone()), false); }); } #[test] - fn can_bond_works() { + fn bond_checks_works() { ExtBuilder::default() .existential_deposit(100) .balance_factor(100) .nominate(false) .build_and_execute(|| { // Amount to bond does not meet ED - assert_eq!(Staking::can_bond(&61, &60, 99, &0), false); + assert_noop!( + Staking::bond_checks(&61, &60, 99, &0), + Error::::InsufficientBond + ); // A ledger already exists for the given controller Ledger::::insert(60, StakingLedger::default_from(61)); - assert_eq!(Staking::can_bond(&61, &60, 100, &0), false); + assert_noop!(Staking::bond_checks(&61, &60, 100, &0), Error::::AlreadyPaired); // The stash is already bonded to a controller Bonded::::insert(71, 70); - assert_eq!(Staking::can_bond(&71, &70, 100, &0), false); + assert_noop!(Staking::bond_checks(&71, &70, 100, &0), Error::::AlreadyBonded); // Cannot increment consumers for stash frame_system::Account::::mutate(&81, |a| { a.providers = 0; }); - assert_eq!(Staking::can_bond(&81, &80, 100, &0), false); + assert_noop!(Staking::bond_checks(&81, &80, 100, &0), Error::::BadState); // Works with valid inputs - assert!(Staking::can_bond(&101, &100, 100, &0)); + assert_ok!(Staking::bond_checks(&101, &100, 100, &0)); - // Clean up so post checks work + // Clean up so test harness post checks work Ledger::::remove(60); Bonded::::remove(71); }); diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index b2fba9b23b825..1ebf8cc9c1d4d 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -101,12 +101,12 @@ pub trait StakingInterface { /// Check if the given accounts can be bonded as stash <-> controller pair and with the given /// reward destination. Does not check if the accounts have enough funds. It is assumed that the /// necessary funds will only be transferred into the accounts after this check is completed. - fn can_bond( + fn bond_checks( stash: &Self::AccountId, controller: &Self::AccountId, value: Self::Balance, payee: &Self::AccountId, - ) -> bool; + ) -> Result<(), DispatchError>; fn bond( stash: Self::AccountId, @@ -116,8 +116,11 @@ pub trait StakingInterface { ) -> DispatchResult; /// Check if the given account can nominate. Assumes the account will be correctly bonded after - /// this call. - fn can_nominate(controller: &Self::AccountId, targets: Vec) -> bool; + /// this call. Returns stash and targets if the checks pass. + fn nominate_checks( + controller: &Self::AccountId, + targets: Vec, + ) -> Result<(Self::AccountId, Vec), DispatchError>; - fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult; + fn unchecked_nominate(controller: Self::AccountId, targets: Vec); } From 92725812916f008ebb7a6fe02aa05ba7a2e53aca Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 27 Jan 2022 15:03:50 -0800 Subject: [PATCH 050/299] fixup tests for *_checks --- frame/staking/src/pallet/impls.rs | 21 ++++++----- frame/staking/src/pallet/mod.rs | 37 +++++++++---------- frame/staking/src/tests.rs | 59 +++++++++++++++++++------------ 3 files changed, 66 insertions(+), 51 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 004f9767d8b83..440fca69a589e 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -143,7 +143,7 @@ impl Pallet { // Nothing to do if they have no reward points. if validator_reward_points.is_zero() { - return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into()); + return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into()) } // This is the fraction of the total reward that the validator and the @@ -235,9 +235,8 @@ impl Pallet { Self::update_ledger(&controller, &l); r }), - RewardDestination::Account(dest_account) => { - Some(T::Currency::deposit_creating(&dest_account, amount)) - }, + RewardDestination::Account(dest_account) => + Some(T::Currency::deposit_creating(&dest_account, amount)), RewardDestination::None => None, } } @@ -265,14 +264,14 @@ impl Pallet { _ => { // Either `Forcing::ForceNone`, // or `Forcing::NotForcing if era_length >= T::SessionsPerEra::get()`. - return None; + return None }, } // New era. let maybe_new_era_validators = Self::try_trigger_new_era(session_index, is_genesis); - if maybe_new_era_validators.is_some() - && matches!(ForceEra::::get(), Forcing::ForceNew) + if maybe_new_era_validators.is_some() && + matches!(ForceEra::::get(), Forcing::ForceNew) { ForceEra::::put(Forcing::NotForcing); } @@ -463,7 +462,7 @@ impl Pallet { } Self::deposit_event(Event::StakingElectionFailed); - return None; + return None } Self::deposit_event(Event::StakersElected); @@ -943,7 +942,7 @@ impl ElectionDataProvider for Pallet { // We can't handle this case yet -- return an error. if maybe_max_len.map_or(false, |max_len| target_count > max_len as u32) { - return Err("Target snapshot too big"); + return Err("Target snapshot too big") } Ok(Self::get_npos_targets()) @@ -1212,7 +1211,7 @@ where add_db_reads_writes(1, 0); if active_era.is_none() { // This offence need not be re-submitted. - return consumed_weight; + return consumed_weight } active_era.expect("value checked not to be `None`; qed").index }; @@ -1258,7 +1257,7 @@ where // Skip if the validator is invulnerable. if invulnerables.contains(stash) { - continue; + continue } let unapplied = slashing::compute_slash::(slashing::SlashParams { diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 395c18d58c7d3..55f5172226d41 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -911,23 +911,24 @@ pub mod pallet { ledger = ledger.consolidate_unlocked(current_era) } - let post_info_weight = - if ledger.unlocking.is_empty() && ledger.active < T::Currency::minimum_balance() { - // This account must have called `unbond()` with some value that caused the active - // portion to fall below existential deposit + will have no more unlocking chunks - // left. We can now safely remove all staking-related information. - Self::kill_stash(&stash, num_slashing_spans)?; - // Remove the lock. - T::Currency::remove_lock(STAKING_ID, &stash); - // This is worst case scenario, so we use the full weight and return None - None - } else { - // This was the consequence of a partial unbond. just update the ledger and move on. - Self::update_ledger(&controller, &ledger); + let post_info_weight = if ledger.unlocking.is_empty() && + ledger.active < T::Currency::minimum_balance() + { + // This account must have called `unbond()` with some value that caused the active + // portion to fall below existential deposit + will have no more unlocking chunks + // left. We can now safely remove all staking-related information. + Self::kill_stash(&stash, num_slashing_spans)?; + // Remove the lock. + T::Currency::remove_lock(STAKING_ID, &stash); + // This is worst case scenario, so we use the full weight and return None + None + } else { + // This was the consequence of a partial unbond. just update the ledger and move on. + Self::update_ledger(&controller, &ledger); - // This is only an update, so we use less overall weight. - Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) - }; + // This is only an update, so we use less overall weight. + Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) + }; // `old_total` should never be less than the new total because // `consolidate_unlocked` strictly subtracts balance. @@ -1409,8 +1410,8 @@ pub mod pallet { let _ = ensure_signed(origin)?; let ed = T::Currency::minimum_balance(); - let reapable = T::Currency::total_balance(&stash) < ed - || Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) + let reapable = T::Currency::total_balance(&stash) < ed || + Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) .map(|l| l.total) .unwrap_or_default() < ed; ensure!(reapable, Error::::FundedTarget); diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 34eec9c29dff9..af992b81913d5 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -275,9 +275,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 - + part_for_100_from_10 * total_payout_0 * 2 / 3 - + part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * total_payout_0 * 2 / 3 + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -313,9 +313,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 - + part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) - + part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -3967,8 +3967,8 @@ mod election_data_provider { #[test] fn targets_2sec_block() { let mut validators = 1000; - while ::WeightInfo::get_npos_targets(validators) - < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_targets(validators) < + 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { validators += 1; } @@ -3985,8 +3985,8 @@ mod election_data_provider { let slashing_spans = validators; let mut nominators = 1000; - while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) - < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) < + 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { nominators += 1; } @@ -4057,8 +4057,8 @@ mod election_data_provider { .build_and_execute(|| { // sum of all nominators who'd be voters (1), plus the self-votes (4). assert_eq!( - ::SortedListProvider::count() - + >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 5 ); @@ -4097,8 +4097,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() - + >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 7 ); @@ -4142,8 +4142,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() - + >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 6 ); @@ -4673,7 +4673,7 @@ mod staking_interface { // Not bonded, so no ledger assert_noop!( Staking::nominate_checks(&80, targets.clone()), - Error::::AlreadyBonded + Error::::NotController ); // Bonded, but below min bond to nominate @@ -4683,7 +4683,10 @@ mod staking_interface { 1_000 - 10, RewardDestination::Controller )); - assert_noop!(Staking::nominate_checks(&80, targets.clone()), false); + assert_noop!( + Staking::nominate_checks(&80, targets.clone()), + Error::::InsufficientBond + ); // Meets min bond, but already at nominator limit assert_ok!(Staking::bond( @@ -4693,16 +4696,25 @@ mod staking_interface { RewardDestination::Controller )); MaxNominatorsCount::::set(Some(0)); - assert_eq!(Staking::nominate_checks(&70, targets.clone()), false); + assert_noop!( + Staking::nominate_checks(&70, targets.clone()), + Error::::TooManyNominators + ); MaxNominatorsCount::::set(None); // Targets are empty let targets = vec![]; - assert_eq!(Staking::nominate_checks(&70, targets.clone()), false); + assert_noop!( + Staking::nominate_checks(&70, targets.clone()), + Error::::EmptyTargets + ); // Too many targets let targets: Vec<_> = (0..=Test::MAX_NOMINATIONS).map(|i| i as u64).collect(); - assert_eq!(Staking::nominate_checks(&70, targets.clone()), false); + assert_noop!( + Staking::nominate_checks(&70, targets.clone()), + Error::::TooManyTargets + ); // A target is blocking assert_ok!(Staking::validate( @@ -4710,7 +4722,10 @@ mod staking_interface { ValidatorPrefs { blocked: true, ..Default::default() } )); let targets = vec![11]; - assert_eq!(Staking::nominate_checks(&70, targets.clone()), false); + assert_noop!( + Staking::nominate_checks(&70, targets.clone()), + Error::::BadTarget + ); }); } From ce55e36a6cb10c95c6279359b16a6c4e8c8c1dec Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 27 Jan 2022 15:20:01 -0800 Subject: [PATCH 051/299] Refactor pools to use updatted StakingInterface --- frame/pools/src/lib.rs | 12 +++--------- frame/pools/src/mock.rs | 22 ++++++++++++++-------- frame/staking/src/pallet/impls.rs | 4 ++-- primitives/staking/src/lib.rs | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 67785c6d6541b..c6c6e3c648c2d 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -662,11 +662,8 @@ pub mod pallet { let (stash, reward_dest) = Self::create_accounts(id); - ensure!( - T::StakingInterface::can_nominate(&stash, &targets) && - T::StakingInterface::can_bond(&stash, &stash, amount, &reward_dest), - Error::::StakingError - ); + T::StakingInterface::bond_checks(&stash, &stash, amount, &reward_dest)?; + let (stash, targets) = T::StakingInterface::nominate_checks(&stash, targets)?; let mut bonded_pool = BondedPool:: { points: Zero::zero(), account_id: stash.clone() }; @@ -690,10 +687,7 @@ pub mod pallet { e })?; - T::StakingInterface::nominate(stash.clone(), targets).map_err(|e| { - log!(warn, "error trying to nominate with a new pool after a users balance was transferred."); - e - })?; + T::StakingInterface::unchecked_nominate(&stash, targets); DelegatorStorage::::insert( who, diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 322f4519caa01..4d551d1e1d7a3 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -72,13 +72,17 @@ impl sp_staking::StakingInterface for StakingMock { Ok(100) } - fn can_bond( + fn bond_checks( _: &Self::AccountId, _: &Self::AccountId, _: Self::Balance, _: &Self::AccountId, - ) -> bool { - CanBond::get() + ) -> Result<(), DispatchError> { + if CanBond::get() { + Ok(()) + } else { + Err(Error::::StakingError.into()) + } } fn bond( @@ -91,13 +95,15 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } - fn can_nominate(_: &Self::AccountId, _: &Vec) -> bool { - CanNominate::get() + fn nominate_checks(controller_and_stash: &Self::AccountId, targets: Vec) -> Result<(Self::AccountId, Vec), DispatchError> { + if CanNominate::get() { + Ok((controller_and_stash.clone(), targets)) + } else { + Err(Error::::StakingError.into()) + } } - fn nominate(_: Self::AccountId, _: Vec) -> DispatchResult { - Ok(()) - } + fn unchecked_nominate(_: &Self::AccountId, _: Vec) {} } impl frame_system::Config for Runtime { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 440fca69a589e..6806625de8aed 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1472,7 +1472,7 @@ impl StakingInterface for Pallet { Self::do_nominate_checks(controller, targets) } - fn unchecked_nominate(stash: Self::AccountId, targets: Vec) { - Self::do_unchecked_nominate_writes(&stash, targets); + fn unchecked_nominate(stash: &Self::AccountId, targets: Vec) { + Self::do_unchecked_nominate_writes(stash, targets); } } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 1ebf8cc9c1d4d..341cc8c41340c 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -122,5 +122,5 @@ pub trait StakingInterface { targets: Vec, ) -> Result<(Self::AccountId, Vec), DispatchError>; - fn unchecked_nominate(controller: Self::AccountId, targets: Vec); + fn unchecked_nominate(stash: &Self::AccountId, targets: Vec); } From 201b3da69a1af8080b13b4629efe54d9dcd8f121 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 27 Jan 2022 18:26:37 -0800 Subject: [PATCH 052/299] Use stash to identify all pool storage items --- frame/pools/src/lib.rs | 187 ++++++++++++++------------ frame/pools/src/mock.rs | 21 ++- frame/pools/src/tests.rs | 283 +++++++++++++++++++++------------------ 3 files changed, 268 insertions(+), 223 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index c6c6e3c648c2d..2443536494a16 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -9,6 +9,13 @@ //! Once a `with_era` pool is older then `current_era - MaxUnbonding`, its points and balance get //! merged into the `no_era` pool. //! +//! # Design goals +//! - Maintain integrity of slashing events in terms of penalizing those backing the validator that +//! equivocated. +//! - Maximizing scalability +//! - Lazy: no parts of the design require any hooks or intervalled upkeep by the chain. All +//! actions can be triggered for a delegator via an extrinsic. +//! //! # Joining //! //! # Claiming rewards @@ -17,12 +24,24 @@ //! //! # Slashing //! -//! # Pool creation +//! # Pool creation and upkeep +//! +//! TBD - possible options: +//! * Pools can be created by anyone but nominations can never be updated +//! * Pools can be created by anyone and the creator can update nominations +//! * Pools are created by governance and governance can update the targets +//! ... Other ideas +//! * pools can have different roles assigned - admin, nominator, destroyer, etc +//! For example: Governance can create a pool and be the admin, then they could +//! assign a nominator like 1KV //! //! # Negatives //! - no voting //! - .. //! +//! # Future features +//! - allow voting via vote splitting +//! //! RUNTIME BUILDER WARNINGS //! - watch out for overflow of `RewardPoints` and `BalanceOf` types. Consider the chains total //! issuance, staking reward rate, and burn rate. @@ -34,7 +53,7 @@ use frame_support::{ pallet_prelude::*, storage::bounded_btree_map::BoundedBTreeMap, traits::{Currency, ExistenceRequirement, Get}, - transactional, DefaultNoBound, RuntimeDebugNoBound, + DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_core::U256; @@ -116,11 +135,11 @@ fn balance_to_unbond( } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] -#[cfg_attr(feature = "std", derive(Clone, PartialEq, DefaultNoBound))] +#[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct Delegator { - pool: PoolId, + pool: T::AccountId, /// The quantity of points this delegator has in the bonded pool or in a sub pool if /// `Self::unbonding_era` is some. points: BalanceOf, @@ -140,29 +159,32 @@ pub struct Delegator { #[scale_info(skip_type_params(T))] pub struct BondedPool { points: BalanceOf, - // The _Stash_ and _Controller_ account for the pool. - account_id: T::AccountId, } impl BondedPool { /// Get the amount of points to issue for some new funds that will be bonded in the pool. - fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { - let bonded_balance = - T::StakingInterface::bonded_balance(&self.account_id).unwrap_or(Zero::zero()); + fn points_to_issue(&self, stash: &T::AccountId, new_funds: BalanceOf) -> BalanceOf { + let bonded_balance = T::StakingInterface::bonded_balance(stash).unwrap_or(Zero::zero()); points_to_issue::(bonded_balance, self.points, new_funds) } // Get the amount of balance to unbond from the pool based on a delegator's points of the pool. - fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { - let bonded_balance = - T::StakingInterface::bonded_balance(&self.account_id).unwrap_or(Zero::zero()); + fn balance_to_unbond( + &self, + stash: &T::AccountId, + delegator_points: BalanceOf, + ) -> BalanceOf { + let bonded_balance = T::StakingInterface::bonded_balance(stash).unwrap_or(Zero::zero()); balance_to_unbond::(bonded_balance, self.points, delegator_points) } // Check that the pool can accept a member with `new_funds`. - fn ok_to_join_with(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { - let bonded_balance = - T::StakingInterface::bonded_balance(&self.account_id).unwrap_or(Zero::zero()); + fn ok_to_join_with( + &self, + stash: &T::AccountId, + new_funds: BalanceOf, + ) -> Result<(), DispatchError> { + let bonded_balance = T::StakingInterface::bonded_balance(stash).unwrap_or(Zero::zero()); ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); let points_to_balance_ratio_floor = self @@ -354,30 +376,31 @@ pub mod pallet { #[pallet::storage] pub(crate) type PoolIds = CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId>; - /// Bonded pools. + /// Bonded pools. Keyed by the pool's _Stash_/_Controller_. #[pallet::storage] pub(crate) type BondedPoolStorage = - CountedStorageMap<_, Twox64Concat, PoolId, BondedPool>; + CountedStorageMap<_, Twox64Concat, T::AccountId, BondedPool>; /// Reward pools. This is where there rewards for each pool accumulate. When a delegators payout - /// is claimed, the balance comes out fo the reward pool. + /// is claimed, the balance comes out fo the reward pool. Keyed by the bonded pools + /// _Stash_/_Controller_. #[pallet::storage] pub(crate) type RewardPoolStorage = - CountedStorageMap<_, Twox64Concat, PoolId, RewardPool>; + CountedStorageMap<_, Twox64Concat, T::AccountId, RewardPool>; /// Groups of unbonding pools. Each group of unbonding pools belongs to a bonded pool, - /// hence the name sub-pools. + /// hence the name sub-pools. Keyed by the bonded pools _Stash_/_Controller_. #[pallet::storage] pub(crate) type SubPoolsStorage = - CountedStorageMap<_, Twox64Concat, PoolId, SubPools>; + CountedStorageMap<_, Twox64Concat, T::AccountId, SubPools>; #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - Joined { delegator: T::AccountId, pool: PoolId, bonded: BalanceOf }, - PaidOut { delegator: T::AccountId, pool: PoolId, payout: BalanceOf }, - Unbonded { delegator: T::AccountId, pool: PoolId, amount: BalanceOf }, - Withdrawn { delegator: T::AccountId, pool: PoolId, amount: BalanceOf }, + Joined { delegator: T::AccountId, pool: T::AccountId, bonded: BalanceOf }, + PaidOut { delegator: T::AccountId, pool: T::AccountId, payout: BalanceOf }, + Unbonded { delegator: T::AccountId, pool: T::AccountId, amount: BalanceOf }, + Withdrawn { delegator: T::AccountId, pool: T::AccountId, amount: BalanceOf }, } #[pallet::error] @@ -402,14 +425,15 @@ pub mod pallet { NotUnbonding, /// Unbonded funds cannot be withdrawn yet because the bond duration has not passed. NotUnbondedYet, - /// The given pool id cannot be used to create a new pool because it is already in use. - IdInUse, /// The amount does not meet the minimum bond to start nominating. MinimumBondNotMet, /// The transaction could not be executed due to overflow risk for the pool. OverflowRisk, /// An error from the staking pallet. StakingError, + // Likely only an error ever encountered in poorly built tests. + /// A pool with the generated account id already exists. + IdInUse, } #[pallet::call] @@ -421,7 +445,11 @@ pub mod pallet { /// * this will *not* dust the delegator account, so the delegator must have at least /// `existential deposit + amount` in their account. #[pallet::weight(666)] - pub fn join(origin: OriginFor, amount: BalanceOf, target: PoolId) -> DispatchResult { + pub fn join( + origin: OriginFor, + amount: BalanceOf, + target: T::AccountId, + ) -> DispatchResult { let who = ensure_signed(origin)?; // If a delegator already exists that means they already belong to a pool ensure!( @@ -430,50 +458,45 @@ pub mod pallet { ); let mut bonded_pool = - BondedPoolStorage::::get(target).ok_or(Error::::PoolNotFound)?; - bonded_pool.ok_to_join_with(amount)?; + BondedPoolStorage::::get(&target).ok_or(Error::::PoolNotFound)?; + bonded_pool.ok_to_join_with(&target, amount)?; // The pool should always be created in such a way its in a state to bond extra, but if // the active balance is slashed below the minimum bonded or the account cannot be // found, we exit early. - if !T::StakingInterface::can_bond_extra(&bonded_pool.account_id, amount) { + if !T::StakingInterface::can_bond_extra(&target, amount) { return Err(Error::::StakingError.into()) } // We don't actually care about writing the reward pool, we just need its // total earnings at this point in time. - let reward_pool = RewardPoolStorage::::get(target) + let reward_pool = RewardPoolStorage::::get(&target) .ok_or(Error::::RewardPoolNotFound)? // This is important because we want the most up-to-date total earnings. .update_total_earnings_and_balance(); - let old_free_balance = T::Currency::free_balance(&bonded_pool.account_id); + let old_free_balance = T::Currency::free_balance(&target); // Transfer the funds to be bonded from `who` to the pools account so the pool can then // go bond them. - T::Currency::transfer( - &who, - &bonded_pool.account_id, - amount, - ExistenceRequirement::KeepAlive, - )?; + T::Currency::transfer(&who, &target, amount, ExistenceRequirement::KeepAlive)?; // Try not to bail after the above transfer // This should now include the transferred balance. - let new_free_balance = T::Currency::free_balance(&bonded_pool.account_id); + let new_free_balance = T::Currency::free_balance(&target); // Get the exact amount we can bond extra. let exact_amount_to_bond = new_free_balance.saturating_sub(old_free_balance); // We must calculate the points to issue *before* we bond `who`'s funds, else the // points:balance ratio will be wrong. - let new_points = bonded_pool.points_to_issue(exact_amount_to_bond); + let new_points = bonded_pool.points_to_issue(&target, exact_amount_to_bond); bonded_pool.points = bonded_pool.points.saturating_add(new_points); - T::StakingInterface::bond_extra(bonded_pool.account_id.clone(), exact_amount_to_bond)?; + T::StakingInterface::bond_extra(target.clone(), exact_amount_to_bond)?; DelegatorStorage::insert( who.clone(), Delegator:: { - pool: target, + pool: target.clone(), points: new_points, // TODO double check that this is ok. // At best the reward pool has the rewards up through the previous era. If the @@ -486,7 +509,7 @@ pub mod pallet { unbonding_era: None, }, ); - BondedPoolStorage::insert(target, bonded_pool); + BondedPoolStorage::insert(&target, bonded_pool); Self::deposit_event(Event::::Joined { delegator: who, @@ -525,7 +548,7 @@ pub mod pallet { let delegator = DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; let mut bonded_pool = - BondedPoolStorage::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; + BondedPoolStorage::::get(&delegator.pool).ok_or(Error::::PoolNotFound)?; // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the bonded pool and thus they can no longer claim their payouts. @@ -536,10 +559,11 @@ pub mod pallet { let mut delegator = DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; // Note that we lazily create the unbonding pools here if they don't already exist - let sub_pools = SubPoolsStorage::::get(delegator.pool).unwrap_or_default(); + let sub_pools = SubPoolsStorage::::get(&delegator.pool).unwrap_or_default(); let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); - let balance_to_unbond = bonded_pool.balance_to_unbond(delegator.points); + let balance_to_unbond = + bonded_pool.balance_to_unbond(&delegator.pool, delegator.points); // Update the bonded pool. Note that we must do this *after* calculating the balance // to unbond so we have the correct points for the balance:share ratio. @@ -547,7 +571,7 @@ pub mod pallet { // TODO: call withdraw unbonded to try and minimize unbonding chunks // Unbond in the actual underlying pool - T::StakingInterface::unbond(bonded_pool.account_id.clone(), balance_to_unbond)?; + T::StakingInterface::unbond(delegator.pool.clone(), balance_to_unbond)?; // Merge any older pools into the general, era agnostic unbond pool. Note that we do // this before inserting to ensure we don't go over the max unbonding pools. @@ -568,13 +592,13 @@ pub mod pallet { Self::deposit_event(Event::::Unbonded { delegator: who.clone(), - pool: delegator.pool, + pool: delegator.pool.clone(), amount: balance_to_unbond, }); // Now that we know everything has worked write the items to storage. - BondedPoolStorage::insert(delegator.pool, bonded_pool); - SubPoolsStorage::insert(delegator.pool, sub_pools); + BondedPoolStorage::insert(&delegator.pool, bonded_pool); + SubPoolsStorage::insert(&delegator.pool, sub_pools); DelegatorStorage::insert(who, delegator); Ok(()) @@ -593,7 +617,7 @@ pub mod pallet { }; let mut sub_pools = - SubPoolsStorage::::get(delegator.pool).ok_or(Error::::SubPoolsNotFound)?; + SubPoolsStorage::::get(&delegator.pool).ok_or(Error::::SubPoolsNotFound)?; let balance_to_unbond = if let Some(pool) = sub_pools.with_era.get_mut(&unbonding_era) { let balance_to_unbond = pool.balance_to_unbond(delegator.points); @@ -612,18 +636,12 @@ pub mod pallet { balance_to_unbond }; - let bonded_pool = - BondedPoolStorage::::get(delegator.pool).ok_or(Error::::PoolNotFound)?; - - if T::Currency::free_balance(&bonded_pool.account_id) < balance_to_unbond { - T::StakingInterface::withdraw_unbonded( - bonded_pool.account_id.clone(), - num_slashing_spans, - )?; + if T::Currency::free_balance(&delegator.pool) < balance_to_unbond { + T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; } T::Currency::transfer( - &bonded_pool.account_id, + &delegator.pool, &who, balance_to_unbond, ExistenceRequirement::AllowDeath, @@ -633,7 +651,7 @@ pub mod pallet { e })?; - SubPoolsStorage::::insert(delegator.pool, sub_pools); + SubPoolsStorage::::insert(&delegator.pool, sub_pools); DelegatorStorage::::remove(&who); Self::deposit_event(Event::::Withdrawn { @@ -645,32 +663,36 @@ pub mod pallet { Ok(()) } - // NOTE: This is transactional because we cannot totally predict if the underlying bond will - // work until we transfer the funds. + // Create a pool. + // + // * `targets`: _Stash_ addresses of the validators to nominate + // * `amount`: Balance to delegate to the pool. Must meet the minimum bond. + // * `index`: Disambiguation index for seeding account generation. Likely only useful when + // creating multiple pools in the same extrinsic. #[pallet::weight(666)] - #[transactional] pub fn create( origin: OriginFor, - id: PoolId, targets: Vec<::Source>, amount: BalanceOf, + index: u16, ) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(!BondedPoolStorage::::contains_key(id), Error::::IdInUse); ensure!(amount >= T::StakingInterface::minimum_bond(), Error::::MinimumBondNotMet); - let (stash, reward_dest) = Self::create_accounts(id); + let (stash, reward_dest) = Self::create_accounts(index); + + println!("stash {:?}, reward_dest {:?}", stash, reward_dest); + ensure!(!BondedPoolStorage::::contains_key(&stash), Error::::IdInUse); T::StakingInterface::bond_checks(&stash, &stash, amount, &reward_dest)?; let (stash, targets) = T::StakingInterface::nominate_checks(&stash, targets)?; - let mut bonded_pool = - BondedPool:: { points: Zero::zero(), account_id: stash.clone() }; + let mut bonded_pool = BondedPool:: { points: Zero::zero() }; // We must calculate the points to issue *before* we bond who's funds, else // points:balance ratio will be wrong. - let points_to_issue = bonded_pool.points_to_issue(amount); + let points_to_issue = bonded_pool.points_to_issue(&stash, amount); bonded_pool.points = points_to_issue; T::Currency::transfer(&who, &stash, amount, ExistenceRequirement::AllowDeath)?; @@ -692,15 +714,15 @@ pub mod pallet { DelegatorStorage::::insert( who, Delegator:: { - pool: id, + pool: stash.clone(), points: points_to_issue, reward_pool_total_earnings: Zero::zero(), unbonding_era: None, }, ); - BondedPoolStorage::::insert(id, bonded_pool); + BondedPoolStorage::::insert(stash.clone(), bonded_pool); RewardPoolStorage::::insert( - id, + stash, RewardPool:: { balance: Zero::zero(), points: U256::zero(), @@ -727,14 +749,14 @@ pub mod pallet { } impl Pallet { - fn create_accounts(id: PoolId) -> (T::AccountId, T::AccountId) { + fn create_accounts(index: u16) -> (T::AccountId, T::AccountId) { let parent_hash = frame_system::Pallet::::parent_hash(); let ext_index = frame_system::Pallet::::extrinsic_index().unwrap_or_default(); let stash_entropy = - (b"pools/stash", id, parent_hash, ext_index).using_encoded(sp_core::blake2_256); + (b"pools/stash", index, parent_hash, ext_index).using_encoded(sp_core::blake2_256); let reward_entropy = - (b"pools/rewards", id, parent_hash, ext_index).using_encoded(sp_core::blake2_256); + (b"pools/rewards", index, parent_hash, ext_index).using_encoded(sp_core::blake2_256); ( Decode::decode(&mut TrailingZeroInput::new(stash_entropy.as_ref())) @@ -806,7 +828,7 @@ impl Pallet { fn transfer_reward( reward_pool: &T::AccountId, delegator: T::AccountId, - pool: PoolId, + pool: T::AccountId, payout: BalanceOf, ) -> Result<(), DispatchError> { T::Currency::transfer(reward_pool, &delegator, payout, ExistenceRequirement::AllowDeath)?; @@ -832,12 +854,12 @@ impl Pallet { Self::transfer_reward( &reward_pool.account_id, delegator_id.clone(), - delegator.pool, + delegator.pool.clone(), delegator_payout, )?; // Write the updated delegator and reward pool to storage - RewardPoolStorage::insert(delegator.pool, reward_pool); + RewardPoolStorage::insert(&delegator.pool, reward_pool); DelegatorStorage::insert(delegator_id, delegator); Ok(()) @@ -855,8 +877,7 @@ impl Pallet { // The current active bonded of the account (i.e. `StakingLedger::active`) active_bonded_balance: BalanceOf, ) -> Option<(BalanceOf, BTreeMap>)> { - let pool_id = PoolIds::::get(pool_account)?; - let mut sub_pools = SubPoolsStorage::::get(pool_id).unwrap_or_default(); + let mut sub_pools = SubPoolsStorage::::get(pool_account).unwrap_or_default(); let affected_range = (slash_era + 1)..=apply_era; @@ -900,7 +921,7 @@ impl Pallet { }) .collect(); - SubPoolsStorage::::insert(pool_id, sub_pools); + SubPoolsStorage::::insert(pool_account, sub_pools); // Equivalent to `(slash_amount / total_affected_balance) * active_bonded_balance` let slashed_bonded_pool_balance = slash_amount diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 4d551d1e1d7a3..ec36a890ffb7e 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -6,10 +6,10 @@ use frame_system::RawOrigin; pub type AccountId = u32; pub type Balance = u128; -/// Pool 0's primary account id (i.e. its stash and controller account with bonded funds). -pub const PRIMARY_ACCOUNT: u32 = 2536596763; -/// Pool 0's reward destination. -pub const REWARDS_ACCOUNT: u32 = 736857005; +/// _Stash_ of the pool that gets created by the ExtBuilder +pub const PRIMARY_ACCOUNT: u32 = 1708226889; +/// Reward destination of the pool that gets created by the ExtBuilder +pub const REWARDS_ACCOUNT: u32 = 1842460259; parameter_types! { pub static CurrentEra: EraIndex = 0; @@ -95,7 +95,10 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } - fn nominate_checks(controller_and_stash: &Self::AccountId, targets: Vec) -> Result<(Self::AccountId, Vec), DispatchError> { + fn nominate_checks( + controller_and_stash: &Self::AccountId, + targets: Vec, + ) -> Result<(Self::AccountId, Vec), DispatchError> { if CanNominate::get() { Ok((controller_and_stash.clone(), targets)) } else { @@ -212,12 +215,16 @@ impl ExtBuilder { // make a pool let amount_to_bond = ::StakingInterface::minimum_bond(); Balances::make_free_balance_be(&10, amount_to_bond * 2); - assert_ok!(Pools::create(RawOrigin::Signed(10).into(), 0, vec![100], amount_to_bond)); + assert_ok!(Pools::create(RawOrigin::Signed(10).into(), vec![100], amount_to_bond, 0)); for (account_id, bonded) in self.delegators { Balances::make_free_balance_be(&account_id, bonded * 2); - assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, 0)); + assert_ok!(Pools::join( + RawOrigin::Signed(account_id).into(), + bonded, + PRIMARY_ACCOUNT + )); } }); diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 957a326e47916..7edcf74dec9dc 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -30,11 +30,11 @@ fn test_setup_works() { assert_eq!(DelegatorStorage::::count(), 1); assert_eq!( - BondedPoolStorage::::get(0).unwrap(), - BondedPool:: { points: 10, account_id: PRIMARY_ACCOUNT } + BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool:: { points: 10 } ); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), RewardPool:: { balance: 0, points: 0.into(), @@ -45,7 +45,7 @@ fn test_setup_works() { assert_eq!( DelegatorStorage::::get(10).unwrap(), Delegator:: { - pool: 0, + pool: PRIMARY_ACCOUNT, points: 10, reward_pool_total_earnings: 0, unbonding_era: None @@ -67,111 +67,111 @@ mod bonded_pool { use super::*; #[test] fn points_to_issue_works() { - let mut bonded_pool = BondedPool:: { points: 100, account_id: 123 }; + let mut bonded_pool = BondedPool:: { points: 100 }; // 1 points : 1 balance ratio StakingMock::set_bonded_balance(123, 100); - assert_eq!(bonded_pool.points_to_issue(10), 10); - assert_eq!(bonded_pool.points_to_issue(0), 0); + assert_eq!(bonded_pool.points_to_issue(&123, 10), 10); + assert_eq!(bonded_pool.points_to_issue(&123, 0), 0); // 2 points : 1 balance ratio StakingMock::set_bonded_balance(123, 50); - assert_eq!(bonded_pool.points_to_issue(10), 20); + assert_eq!(bonded_pool.points_to_issue(&123, 10), 20); // 1 points : 2 balance ratio StakingMock::set_bonded_balance(123, 100); bonded_pool.points = 50; - assert_eq!(bonded_pool.points_to_issue(10), 5); + assert_eq!(bonded_pool.points_to_issue(&123, 10), 5); // 100 points : 0 balance ratio StakingMock::set_bonded_balance(123, 0); bonded_pool.points = 100; - assert_eq!(bonded_pool.points_to_issue(10), 100 * 10); + assert_eq!(bonded_pool.points_to_issue(&123, 10), 100 * 10); // 0 points : 100 balance StakingMock::set_bonded_balance(123, 100); bonded_pool.points = 100; - assert_eq!(bonded_pool.points_to_issue(10), 10); + assert_eq!(bonded_pool.points_to_issue(&123, 10), 10); // 10 points : 3 balance ratio StakingMock::set_bonded_balance(123, 30); - assert_eq!(bonded_pool.points_to_issue(10), 33); + assert_eq!(bonded_pool.points_to_issue(&123, 10), 33); // 2 points : 3 balance ratio StakingMock::set_bonded_balance(123, 300); bonded_pool.points = 200; - assert_eq!(bonded_pool.points_to_issue(10), 6); + assert_eq!(bonded_pool.points_to_issue(&123, 10), 6); // 4 points : 9 balance ratio StakingMock::set_bonded_balance(123, 900); bonded_pool.points = 400; - assert_eq!(bonded_pool.points_to_issue(90), 40); + assert_eq!(bonded_pool.points_to_issue(&123, 90), 40); } #[test] fn balance_to_unbond_works() { // 1 balance : 1 points ratio - let mut bonded_pool = BondedPool:: { points: 100, account_id: 123 }; + let mut bonded_pool = BondedPool:: { points: 100 }; StakingMock::set_bonded_balance(123, 100); - assert_eq!(bonded_pool.balance_to_unbond(10), 10); - assert_eq!(bonded_pool.balance_to_unbond(0), 0); + assert_eq!(bonded_pool.balance_to_unbond(&123, 10), 10); + assert_eq!(bonded_pool.balance_to_unbond(&123, 0), 0); // 2 balance : 1 points ratio bonded_pool.points = 50; - assert_eq!(bonded_pool.balance_to_unbond(10), 20); + assert_eq!(bonded_pool.balance_to_unbond(&123, 10), 20); // 100 balance : 0 points ratio StakingMock::set_bonded_balance(123, 0); bonded_pool.points = 0; - assert_eq!(bonded_pool.balance_to_unbond(10), 0); + assert_eq!(bonded_pool.balance_to_unbond(&123, 10), 0); // 0 balance : 100 points ratio StakingMock::set_bonded_balance(123, 0); bonded_pool.points = 100; - assert_eq!(bonded_pool.balance_to_unbond(10), 0); + assert_eq!(bonded_pool.balance_to_unbond(&123, 10), 0); // 10 balance : 3 points ratio StakingMock::set_bonded_balance(123, 100); bonded_pool.points = 30; - assert_eq!(bonded_pool.balance_to_unbond(10), 33); + assert_eq!(bonded_pool.balance_to_unbond(&123, 10), 33); // 2 balance : 3 points ratio StakingMock::set_bonded_balance(123, 200); bonded_pool.points = 300; - assert_eq!(bonded_pool.balance_to_unbond(10), 6); + assert_eq!(bonded_pool.balance_to_unbond(&123, 10), 6); // 4 balance : 9 points ratio StakingMock::set_bonded_balance(123, 400); bonded_pool.points = 900; - assert_eq!(bonded_pool.balance_to_unbond(90), 40); + assert_eq!(bonded_pool.balance_to_unbond(&123, 90), 40); } #[test] fn ok_to_join_with_works() { ExtBuilder::default().build_and_execute(|| { - let pool = BondedPool:: { points: 100, account_id: 123 }; + let pool = BondedPool:: { points: 100 }; // Simulate a 100% slashed pool StakingMock::set_bonded_balance(123, 0); - assert_noop!(pool.ok_to_join_with(100), Error::::OverflowRisk); + assert_noop!(pool.ok_to_join_with(&123, 100), Error::::OverflowRisk); // Simulate a 89% StakingMock::set_bonded_balance(123, 11); - assert_ok!(pool.ok_to_join_with(100)); + assert_ok!(pool.ok_to_join_with(&123, 100)); // Simulate a 90% slashed pool StakingMock::set_bonded_balance(123, 10); - assert_noop!(pool.ok_to_join_with(100), Error::::OverflowRisk); + assert_noop!(pool.ok_to_join_with(&123, 100), Error::::OverflowRisk); let bonded = 100; StakingMock::set_bonded_balance(123, bonded); // New bonded balance would be over 1/10th of Balance type assert_noop!( - pool.ok_to_join_with(Balance::MAX / 10 - bonded), + pool.ok_to_join_with(&123, Balance::MAX / 10 - bonded), Error::::OverflowRisk ); // and a sanity check - assert_ok!(pool.ok_to_join_with(Balance::MAX / 100 - bonded + 1),); + assert_ok!(pool.ok_to_join_with(&123, Balance::MAX / 100 - bonded + 1),); }); } } @@ -357,21 +357,21 @@ mod join { assert!(!DelegatorStorage::::contains_key(&11)); // When - assert_ok!(Pools::join(Origin::signed(11), 2, 0)); + assert_ok!(Pools::join(Origin::signed(11), 2, PRIMARY_ACCOUNT)); // then assert_eq!( DelegatorStorage::::get(&11).unwrap(), Delegator:: { - pool: 0, + pool: PRIMARY_ACCOUNT, points: 2, reward_pool_total_earnings: 0, unbonding_era: None } ); assert_eq!( - BondedPoolStorage::::get(&0).unwrap(), - BondedPool:: { points: 12, account_id: PRIMARY_ACCOUNT } + BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool:: { points: 12 } ); // Given @@ -382,21 +382,21 @@ mod join { assert!(!DelegatorStorage::::contains_key(&12)); // When - assert_ok!(Pools::join(Origin::signed(12), 12, 0)); + assert_ok!(Pools::join(Origin::signed(12), 12, PRIMARY_ACCOUNT)); // Then assert_eq!( DelegatorStorage::::get(&12).unwrap(), Delegator:: { - pool: 0, + pool: PRIMARY_ACCOUNT, points: 24, reward_pool_total_earnings: 0, unbonding_era: None } ); assert_eq!( - BondedPoolStorage::::get(&0).unwrap(), - BondedPool:: { points: 12 + 24, account_id: PRIMARY_ACCOUNT } + BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool:: { points: 12 + 24 } ); }); } @@ -414,30 +414,30 @@ mod join { // Force the pools bonded balance to 0, simulating a 100% slash StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 0); - assert_noop!(Pools::join(Origin::signed(11), 420, 0), Error::::OverflowRisk); - - BondedPoolStorage::::insert( - 1, - BondedPool:: { points: 100, account_id: 123 }, + assert_noop!( + Pools::join(Origin::signed(11), 420, PRIMARY_ACCOUNT), + Error::::OverflowRisk ); + + BondedPoolStorage::::insert(123, BondedPool:: { points: 100 }); // Force the points:balance ratio to 100/10 (so 10) StakingMock::set_bonded_balance(123, 10); - assert_noop!(Pools::join(Origin::signed(11), 420, 1), Error::::OverflowRisk); + assert_noop!(Pools::join(Origin::signed(11), 420, 123), Error::::OverflowRisk); // Force the points:balance ratio to be a valid 100/100 StakingMock::set_bonded_balance(123, 100); // Cumulative balance is > 1/10 of Balance::MAX assert_noop!( - Pools::join(Origin::signed(11), Balance::MAX / 10 - 100, 1), + Pools::join(Origin::signed(11), Balance::MAX / 10 - 100, 123), Error::::OverflowRisk ); CanBondExtra::set(false); - assert_noop!(Pools::join(Origin::signed(11), 420, 1), Error::::StakingError); + assert_noop!(Pools::join(Origin::signed(11), 420, 123), Error::::StakingError); CanBondExtra::set(true); assert_noop!( - Pools::join(Origin::signed(11), 420, 1), + Pools::join(Origin::signed(11), 420, 123), Error::::RewardPoolNotFound ); RewardPoolStorage::::insert( @@ -457,7 +457,7 @@ mod claim_payout { use super::*; fn del(points: Balance, reward_pool_total_earnings: Balance) -> Delegator { - Delegator { pool: 0, points, reward_pool_total_earnings, unbonding_era: None } + Delegator { pool: PRIMARY_ACCOUNT, points, reward_pool_total_earnings, unbonding_era: None } } fn rew(balance: Balance, points: u32, total_earnings: Balance) -> RewardPool { @@ -484,7 +484,7 @@ mod claim_payout { // balance assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 100)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(90, 100 * 100 - 100 * 10, 100) ); assert_eq!(Balances::free_balance(&10), 10); @@ -497,7 +497,7 @@ mod claim_payout { // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 100)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(50, 9_000 - 100 * 40, 100) ); assert_eq!(Balances::free_balance(&40), 40); @@ -509,7 +509,10 @@ mod claim_payout { // Then // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 100)); - assert_eq!(RewardPoolStorage::::get(0).unwrap(), rew(0, 0, 100)); + assert_eq!( + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + rew(0, 0, 100) + ); assert_eq!(Balances::free_balance(&50), 50); assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); @@ -523,7 +526,7 @@ mod claim_payout { // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 150)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(45, 5_000 - 50 * 10, 150) ); assert_eq!(Balances::free_balance(&10), 10 + 5); @@ -537,7 +540,7 @@ mod claim_payout { // balance assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 150)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(25, 4_500 - 50 * 40, 150) ); assert_eq!(Balances::free_balance(&40), 40 + 20); @@ -555,7 +558,7 @@ mod claim_payout { // pool balance assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 200)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew( 25, // old pool points + points from new earnings - del points. @@ -576,7 +579,7 @@ mod claim_payout { // We expect a payout of 5 assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 200)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(20, 2_500 - 10 * 50, 200) ); assert_eq!(Balances::free_balance(&10), 15 + 5); @@ -593,7 +596,7 @@ mod claim_payout { // We expect a payout of 40 assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 600)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew( 380, // old pool points + points from new earnings - del points @@ -619,7 +622,7 @@ mod claim_payout { // balance assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 620)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(398, (38_000 + 20 * 100) - 10 * 20, 620) ); assert_eq!(Balances::free_balance(&10), 60 + 2); @@ -633,7 +636,7 @@ mod claim_payout { // pool balance assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 620)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(210, 39_800 - 40 * 470, 620) ); assert_eq!(Balances::free_balance(&40), 60 + 188); @@ -646,7 +649,7 @@ mod claim_payout { // Expect payout of 210: (21,000 / 21,000) * 210 assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 620)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(0, 21_000 - 50 * 420, 620) ); assert_eq!(Balances::free_balance(&50), 100 + 210); @@ -657,8 +660,8 @@ mod claim_payout { #[test] fn calculate_delegator_payout_errors_if_a_delegator_is_unbonding() { ExtBuilder::default().build_and_execute(|| { - let bonded_pool = BondedPoolStorage::::get(0).unwrap(); - let reward_pool = RewardPoolStorage::::get(0).unwrap(); + let bonded_pool = BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); + let reward_pool = RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); let mut delegator = DelegatorStorage::::get(10).unwrap(); delegator.unbonding_era = Some(0); @@ -671,15 +674,15 @@ mod claim_payout { #[test] fn calculate_delegator_payout_works_with_a_pool_of_1() { - let del = |reward_pool_total_earnings| Delegator:: { - pool: 0, - points: 10, - reward_pool_total_earnings, - unbonding_era: None, - }; + let del = |reward_pool_total_earnings| del(10, reward_pool_total_earnings); + // pool: PRIMARY_ACCOUNT, + // points: 10, + // reward_pool_total_earnings, + // unbonding_era: None, + // }; ExtBuilder::default().build_and_execute(|| { - let bonded_pool = BondedPoolStorage::::get(0).unwrap(); - let reward_pool = RewardPoolStorage::::get(0).unwrap(); + let bonded_pool = BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); + let reward_pool = RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); let delegator = DelegatorStorage::::get(10).unwrap(); // Given no rewards have been earned @@ -735,8 +738,8 @@ mod claim_payout { ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - let bonded_pool = BondedPoolStorage::::get(0).unwrap(); - let reward_pool = RewardPoolStorage::::get(0).unwrap(); + let bonded_pool = BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); + let reward_pool = RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); // Delegator with 10 points let del_10 = DelegatorStorage::::get(10).unwrap(); // Delegator with 40 points @@ -920,7 +923,7 @@ mod claim_payout { ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - let bonded_pool = BondedPoolStorage::::get(0).unwrap(); + let bonded_pool = BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); // Given the bonded pool has 100 points assert_eq!(bonded_pool.points, 100); @@ -943,7 +946,7 @@ mod claim_payout { // balance assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 100)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(90, 100 * 100 - 100 * 10, 100) ); assert_eq!(Balances::free_balance(&10), 10); @@ -960,7 +963,7 @@ mod claim_payout { // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 100)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(50, 9_000 - 100 * 40, 100) ); assert_eq!(Balances::free_balance(&40), 40); @@ -976,7 +979,10 @@ mod claim_payout { // Then // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 100)); - assert_eq!(RewardPoolStorage::::get(0).unwrap(), rew(0, 0, 100)); + assert_eq!( + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + rew(0, 0, 100) + ); assert_eq!(Balances::free_balance(&50), 50); assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); @@ -994,7 +1000,7 @@ mod claim_payout { // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 150)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(45, 5_000 - 50 * 10, 150) ); assert_eq!(Balances::free_balance(&10), 10 + 5); @@ -1012,7 +1018,7 @@ mod claim_payout { // balance assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 150)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(25, 4_500 - 50 * 40, 150) ); assert_eq!(Balances::free_balance(&40), 40 + 20); @@ -1034,7 +1040,7 @@ mod claim_payout { // pool balance assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 200)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew( 25, // old pool points + points from new earnings - del points. @@ -1059,7 +1065,7 @@ mod claim_payout { // We expect a payout of 5 assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 200)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(20, 2_500 - 10 * 50, 200) ); assert_eq!(Balances::free_balance(&10), 15 + 5); @@ -1080,7 +1086,7 @@ mod claim_payout { // We expect a payout of 40 assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 600)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew( 380, // old pool points + points from new earnings - del points @@ -1110,7 +1116,7 @@ mod claim_payout { // balance assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 620)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(398, (38_000 + 20 * 100) - 10 * 20, 620) ); assert_eq!(Balances::free_balance(&10), 60 + 2); @@ -1128,7 +1134,7 @@ mod claim_payout { // pool balance assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 620)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(210, 39_800 - 40 * 470, 620) ); assert_eq!(Balances::free_balance(&40), 60 + 188); @@ -1145,7 +1151,7 @@ mod claim_payout { // Expect payout of 210: (21,000 / 21,000) * 210 assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 620)); assert_eq!( - RewardPoolStorage::::get(0).unwrap(), + RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), rew(0, 21_000 - 50 * 420, 620) ); assert_eq!(Balances::free_balance(&50), 100 + 210); @@ -1156,7 +1162,7 @@ mod claim_payout { #[test] fn do_reward_payout_errors_correctly() { ExtBuilder::default().build_and_execute(|| { - let bonded_pool = BondedPoolStorage::::get(0).unwrap(); + let bonded_pool = BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); let mut delegator = DelegatorStorage::::get(10).unwrap(); // The only place this can return an error is with the balance transfer from the @@ -1187,13 +1193,13 @@ mod unbond { assert_ok!(Pools::unbond(Origin::signed(10))); assert_eq!( - SubPoolsStorage::::get(0).unwrap().with_era, + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, sub_pools_with_era! { 0 => UnbondPool:: { points: 10, balance: 10 }} ); assert_eq!( - BondedPoolStorage::::get(0).unwrap(), - BondedPool { account_id: PRIMARY_ACCOUNT, points: 0 } + BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool { points: 0 } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); @@ -1215,12 +1221,12 @@ mod unbond { // Then assert_eq!( - SubPoolsStorage::::get(0).unwrap().with_era, + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, sub_pools_with_era! { 0 => UnbondPool { points: 6, balance: 6 }} ); assert_eq!( - BondedPoolStorage::::get(0).unwrap(), - BondedPool { account_id: PRIMARY_ACCOUNT, points: 560 } + BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool { points: 560 } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 94); assert_eq!(DelegatorStorage::::get(40).unwrap().unbonding_era, Some(0)); @@ -1231,12 +1237,12 @@ mod unbond { // Then assert_eq!( - SubPoolsStorage::::get(0).unwrap().with_era, + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, sub_pools_with_era! { 0 => UnbondPool { points: 7, balance: 7 }} ); assert_eq!( - BondedPoolStorage::::get(0).unwrap(), - BondedPool { account_id: PRIMARY_ACCOUNT, points: 550 } + BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool { points: 550 } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 93); assert_eq!(DelegatorStorage::::get(10).unwrap().unbonding_era, Some(0)); @@ -1247,12 +1253,12 @@ mod unbond { // Then assert_eq!( - SubPoolsStorage::::get(0).unwrap().with_era, + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, sub_pools_with_era! { 0 => UnbondPool { points: 100, balance: 100 }} ); assert_eq!( - BondedPoolStorage::::get(0).unwrap(), - BondedPool { account_id: PRIMARY_ACCOUNT, points: 0 } + BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool { points: 0 } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); assert_eq!(DelegatorStorage::::get(550).unwrap().unbonding_era, Some(0)); @@ -1265,7 +1271,7 @@ mod unbond { ExtBuilder::default().build_and_execute(|| { // Given SubPoolsStorage::::insert( - 0, + PRIMARY_ACCOUNT, SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { @@ -1284,7 +1290,7 @@ mod unbond { // Then assert_eq!( - SubPoolsStorage::::get(0).unwrap(), + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), SubPools { no_era: UnbondPool { balance: 10 + 20, points: 100 + 20 }, with_era: sub_pools_with_era! { @@ -1302,13 +1308,18 @@ mod unbond { assert_noop!(Pools::unbond(Origin::signed(11)), Error::::DelegatorNotFound); // Add the delegator - let delegator = Delegator { pool: 1, points: 10, ..Default::default() }; + let delegator = Delegator { + pool: 1, + points: 10, + reward_pool_total_earnings: 0, + unbonding_era: None, + }; DelegatorStorage::::insert(11, delegator); assert_noop!(Pools::unbond(Origin::signed(11)), Error::::PoolNotFound); // Add bonded pool to go along with the delegator - let bonded_pool = BondedPool { account_id: 101, points: 10 }; + let bonded_pool = BondedPool { points: 10 }; BondedPoolStorage::::insert(1, bonded_pool); assert_noop!(Pools::unbond(Origin::signed(11)), Error::::RewardPoolNotFound); @@ -1337,7 +1348,7 @@ mod withdraw_unbonded { assert_ok!(Pools::unbond(Origin::signed(550))); // Simulate a slash to the pool with_era(current_era) - let mut sub_pools = SubPoolsStorage::::get(0).unwrap(); + let mut sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(); let unbond_pool = sub_pools.with_era.get_mut(¤t_era).unwrap(); // Sanity check @@ -1345,7 +1356,7 @@ mod withdraw_unbonded { // Decrease the balance by half unbond_pool.balance = 275; - SubPoolsStorage::::insert(0, sub_pools); + SubPoolsStorage::::insert(PRIMARY_ACCOUNT, sub_pools); // Update the equivalent of the unbonding chunks for the `StakingMock` UNBONDING_BALANCE_MAP @@ -1358,8 +1369,9 @@ mod withdraw_unbonded { // Simulate some other call to unbond that would merge `with_era` pools into // `no_era` - let sub_pools = - SubPoolsStorage::::get(0).unwrap().maybe_merge_pools(current_era); + let sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT) + .unwrap() + .maybe_merge_pools(current_era); assert_eq!( sub_pools, SubPools { @@ -1367,14 +1379,14 @@ mod withdraw_unbonded { no_era: UnbondPool { balance: 275 + 40 + 10, points: 550 + 40 + 10 } } ); - SubPoolsStorage::::insert(0, sub_pools); + SubPoolsStorage::::insert(PRIMARY_ACCOUNT, sub_pools); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 0)); // Then assert_eq!( - SubPoolsStorage::::get(0).unwrap().no_era, + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().no_era, UnbondPool { points: 40 + 10, balance: 275 + 40 + 10 - 297 } ); assert_eq!(Balances::free_balance(&550), 550 + 297); @@ -1386,7 +1398,7 @@ mod withdraw_unbonded { // Then assert_eq!( - SubPoolsStorage::::get(0).unwrap().no_era, + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().no_era, UnbondPool { points: 10, balance: 28 - 22 } ); assert_eq!(Balances::free_balance(&40), 40 + 22); @@ -1398,7 +1410,7 @@ mod withdraw_unbonded { // Then assert_eq!( - SubPoolsStorage::::get(0).unwrap().no_era, + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().no_era, UnbondPool { points: 0, balance: 0 } ); assert_eq!(Balances::free_balance(&10), 10 + 6); @@ -1419,7 +1431,7 @@ mod withdraw_unbonded { assert_ok!(Pools::unbond(Origin::signed(40))); assert_ok!(Pools::unbond(Origin::signed(550))); SubPoolsStorage::::insert( - 0, + PRIMARY_ACCOUNT, SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { 0 => UnbondPool { points: 600, balance: 100 }}, @@ -1432,7 +1444,7 @@ mod withdraw_unbonded { // Then assert_eq!( - SubPoolsStorage::::get(0).unwrap().with_era, + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, sub_pools_with_era! { 0 => UnbondPool { points: 560, balance: 94 }} ); assert_eq!(Balances::free_balance(&40), 40 + 6); @@ -1444,7 +1456,7 @@ mod withdraw_unbonded { // Then assert_eq!( - SubPoolsStorage::::get(0).unwrap().with_era, + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, sub_pools_with_era! { 0 => UnbondPool { points: 550, balance: 93 }} ); assert_eq!(Balances::free_balance(&10), 10 + 1); @@ -1456,7 +1468,7 @@ mod withdraw_unbonded { // Then assert_eq!( - SubPoolsStorage::::get(0).unwrap().with_era, + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, sub_pools_with_era! { 0 => UnbondPool { points: 0, balance: 0 }} ); assert_eq!(Balances::free_balance(&550), 550 + 93); @@ -1474,45 +1486,45 @@ mod withdraw_unbonded { ); let mut delegator = Delegator { - pool: 1, + pool: 123, points: 10, reward_pool_total_earnings: 0, unbonding_era: None, }; DelegatorStorage::::insert(11, delegator.clone()); + // The delegator has not called `unbond` assert_noop!( Pools::withdraw_unbonded(Origin::signed(11), 0), Error::::NotUnbonding ); + // Simulate calling `unbond` delegator.unbonding_era = Some(0); DelegatorStorage::::insert(11, delegator.clone()); + // We are still in the bonding duration assert_noop!( Pools::withdraw_unbonded(Origin::signed(11), 0), Error::::NotUnbondedYet ); + // Skip ahead to the end of the bonding duration CurrentEra::set(StakingMock::bonding_duration()); + // We encounter a system logic error;` the sub-pools would normally be created in + // `unbond` assert_noop!( Pools::withdraw_unbonded(Origin::signed(11), 0), Error::::SubPoolsNotFound ); + // Insert the sub-pool let sub_pools = SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { 0 => UnbondPool { points: 10, balance: 10 }}, }; - SubPoolsStorage::::insert(1, sub_pools.clone()); - - assert_noop!( - Pools::withdraw_unbonded(Origin::signed(11), 0), - Error::::PoolNotFound - ); - BondedPoolStorage::::insert(1, BondedPool { points: 0, account_id: 123 }); - assert_eq!(Balances::free_balance(&123), 0); + SubPoolsStorage::::insert(123, sub_pools.clone()); assert_noop!( Pools::withdraw_unbonded(Origin::signed(11), 0), @@ -1522,7 +1534,7 @@ mod withdraw_unbonded { // If we error the delegator does not get removed assert_eq!(DelegatorStorage::::get(&11), Some(delegator)); // and the subpools do not get updated. - assert_eq!(SubPoolsStorage::::get(1).unwrap(), sub_pools) + assert_eq!(SubPoolsStorage::::get(123).unwrap(), sub_pools) }); } } @@ -1533,7 +1545,7 @@ mod create { #[test] fn create_works() { ExtBuilder::default().build_and_execute(|| { - let bonded_account = 290807105; + let bonded_account = 337692978; assert!(!BondedPoolStorage::::contains_key(1)); assert!(!RewardPoolStorage::::contains_key(1)); @@ -1541,33 +1553,34 @@ mod create { assert_eq!(StakingMock::bonded_balance(&bonded_account), None); Balances::make_free_balance_be(&11, StakingMock::minimum_bond()); - assert_ok!(Pools::create(Origin::signed(11), 1, vec![], StakingMock::minimum_bond())); + println!("a"); + assert_ok!(Pools::create(Origin::signed(11), vec![], StakingMock::minimum_bond(), 42)); assert_eq!(Balances::free_balance(&11), 0); assert_eq!( DelegatorStorage::::get(11).unwrap(), Delegator { - pool: 1, + pool: bonded_account, points: StakingMock::minimum_bond(), reward_pool_total_earnings: Zero::zero(), unbonding_era: None } ); assert_eq!( - BondedPoolStorage::::get(1).unwrap(), - BondedPool { points: StakingMock::minimum_bond(), account_id: bonded_account } + BondedPoolStorage::::get(1708226889).unwrap(), + BondedPool { points: StakingMock::minimum_bond() } ); assert_eq!( StakingMock::bonded_balance(&bonded_account).unwrap(), StakingMock::minimum_bond() ); assert_eq!( - RewardPoolStorage::::get(1).unwrap(), + RewardPoolStorage::::get(1708226889).unwrap(), RewardPool { balance: Zero::zero(), points: U256::zero(), total_earnings: Zero::zero(), - account_id: 3354943642 + account_id: 1842460259 } ); }); @@ -1577,25 +1590,25 @@ mod create { fn create_errors_correctly() { ExtBuilder::default().build_and_execute(|| { assert_noop!( - Pools::create(Origin::signed(11), 0, vec![], 420), + Pools::create(Origin::signed(11), vec![], 420, 0), Error::::IdInUse ); assert_noop!( - Pools::create(Origin::signed(11), 1, vec![], 1), + Pools::create(Origin::signed(11), vec![], 1, 42), Error::::MinimumBondNotMet ); CanNominate::set(false); assert_noop!( - Pools::create(Origin::signed(11), 1, vec![], 420), + Pools::create(Origin::signed(11), vec![], 420, 42), Error::::StakingError ); CanNominate::set(true); CanBond::set(false); assert_noop!( - Pools::create(Origin::signed(11), 1, vec![], 420), + Pools::create(Origin::signed(11), vec![], 420, 42), Error::::StakingError ); CanBond::set(true); @@ -1604,6 +1617,10 @@ mod create { } mod pools_interface { + use super::*; + #[test] - fn slash_pool_works() {} + fn slash_pool_works() { + ExtBuilder::default().build_and_execute(|| {}); + } } From 732b991275103071fc7bd408c79e05626e44ce4c Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 27 Jan 2022 18:44:34 -0800 Subject: [PATCH 053/299] Remove pointless postfix from storage item names --- frame/pools/src/lib.rs | 49 +++++---- frame/pools/src/tests.rs | 225 +++++++++++++++++++-------------------- 2 files changed, 136 insertions(+), 138 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 2443536494a16..1a04e4dc626db 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -368,7 +368,7 @@ pub mod pallet { /// Active delegators. #[pallet::storage] - pub(crate) type DelegatorStorage = + pub(crate) type Delegators = CountedStorageMap<_, Twox64Concat, T::AccountId, Delegator>; /// `PoolId` lookup from the pool's `AccountId`. Useful for pool lookup from the slashing @@ -378,14 +378,14 @@ pub mod pallet { /// Bonded pools. Keyed by the pool's _Stash_/_Controller_. #[pallet::storage] - pub(crate) type BondedPoolStorage = + pub(crate) type BondedPools = CountedStorageMap<_, Twox64Concat, T::AccountId, BondedPool>; /// Reward pools. This is where there rewards for each pool accumulate. When a delegators payout /// is claimed, the balance comes out fo the reward pool. Keyed by the bonded pools /// _Stash_/_Controller_. #[pallet::storage] - pub(crate) type RewardPoolStorage = + pub(crate) type RewardPools = CountedStorageMap<_, Twox64Concat, T::AccountId, RewardPool>; /// Groups of unbonding pools. Each group of unbonding pools belongs to a bonded pool, @@ -453,12 +453,12 @@ pub mod pallet { let who = ensure_signed(origin)?; // If a delegator already exists that means they already belong to a pool ensure!( - !DelegatorStorage::::contains_key(&who), + !Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool ); let mut bonded_pool = - BondedPoolStorage::::get(&target).ok_or(Error::::PoolNotFound)?; + BondedPools::::get(&target).ok_or(Error::::PoolNotFound)?; bonded_pool.ok_to_join_with(&target, amount)?; // The pool should always be created in such a way its in a state to bond extra, but if @@ -470,7 +470,7 @@ pub mod pallet { // We don't actually care about writing the reward pool, we just need its // total earnings at this point in time. - let reward_pool = RewardPoolStorage::::get(&target) + let reward_pool = RewardPools::::get(&target) .ok_or(Error::::RewardPoolNotFound)? // This is important because we want the most up-to-date total earnings. .update_total_earnings_and_balance(); @@ -493,7 +493,7 @@ pub mod pallet { T::StakingInterface::bond_extra(target.clone(), exact_amount_to_bond)?; - DelegatorStorage::insert( + Delegators::insert( who.clone(), Delegator:: { pool: target.clone(), @@ -509,7 +509,7 @@ pub mod pallet { unbonding_era: None, }, ); - BondedPoolStorage::insert(&target, bonded_pool); + BondedPools::insert(&target, bonded_pool); Self::deposit_event(Event::::Joined { delegator: who, @@ -529,8 +529,8 @@ pub mod pallet { pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let delegator = - DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; - let bonded_pool = BondedPoolStorage::::get(&delegator.pool).ok_or_else(|| { + Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let bonded_pool = BondedPools::::get(&delegator.pool).ok_or_else(|| { log!(error, "A bonded pool could not be found, this is a system logic error."); Error::::PoolNotFound })?; @@ -546,9 +546,9 @@ pub mod pallet { pub fn unbond(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let delegator = - DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; + Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; let mut bonded_pool = - BondedPoolStorage::::get(&delegator.pool).ok_or(Error::::PoolNotFound)?; + BondedPools::::get(&delegator.pool).ok_or(Error::::PoolNotFound)?; // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the bonded pool and thus they can no longer claim their payouts. @@ -557,7 +557,7 @@ pub mod pallet { // Re-fetch the delegator because they where updated by `do_reward_payout`. let mut delegator = - DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; + Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPoolsStorage::::get(&delegator.pool).unwrap_or_default(); let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); @@ -597,9 +597,9 @@ pub mod pallet { }); // Now that we know everything has worked write the items to storage. - BondedPoolStorage::insert(&delegator.pool, bonded_pool); + BondedPools::insert(&delegator.pool, bonded_pool); SubPoolsStorage::insert(&delegator.pool, sub_pools); - DelegatorStorage::insert(who, delegator); + Delegators::insert(who, delegator); Ok(()) } @@ -608,7 +608,7 @@ pub mod pallet { pub fn withdraw_unbonded(origin: OriginFor, num_slashing_spans: u32) -> DispatchResult { let who = ensure_signed(origin)?; let delegator = - DelegatorStorage::::get(&who).ok_or(Error::::DelegatorNotFound)?; + Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); @@ -652,7 +652,7 @@ pub mod pallet { })?; SubPoolsStorage::::insert(&delegator.pool, sub_pools); - DelegatorStorage::::remove(&who); + Delegators::::remove(&who); Self::deposit_event(Event::::Withdrawn { delegator: who, @@ -682,8 +682,7 @@ pub mod pallet { let (stash, reward_dest) = Self::create_accounts(index); - println!("stash {:?}, reward_dest {:?}", stash, reward_dest); - ensure!(!BondedPoolStorage::::contains_key(&stash), Error::::IdInUse); + ensure!(!BondedPools::::contains_key(&stash), Error::::IdInUse); T::StakingInterface::bond_checks(&stash, &stash, amount, &reward_dest)?; let (stash, targets) = T::StakingInterface::nominate_checks(&stash, targets)?; @@ -711,7 +710,7 @@ pub mod pallet { T::StakingInterface::unchecked_nominate(&stash, targets); - DelegatorStorage::::insert( + Delegators::::insert( who, Delegator:: { pool: stash.clone(), @@ -720,8 +719,8 @@ pub mod pallet { unbonding_era: None, }, ); - BondedPoolStorage::::insert(stash.clone(), bonded_pool); - RewardPoolStorage::::insert( + BondedPools::::insert(stash.clone(), bonded_pool); + RewardPools::::insert( stash, RewardPool:: { balance: Zero::zero(), @@ -842,7 +841,7 @@ impl Pallet { delegator: Delegator, bonded_pool: &BondedPool, ) -> DispatchResult { - let reward_pool = RewardPoolStorage::::get(&delegator.pool).ok_or_else(|| { + let reward_pool = RewardPools::::get(&delegator.pool).ok_or_else(|| { log!(error, "A reward pool could not be found, this is a system logic error."); Error::::RewardPoolNotFound })?; @@ -859,8 +858,8 @@ impl Pallet { )?; // Write the updated delegator and reward pool to storage - RewardPoolStorage::insert(&delegator.pool, reward_pool); - DelegatorStorage::insert(delegator_id, delegator); + RewardPools::insert(&delegator.pool, reward_pool); + Delegators::insert(delegator_id, delegator); Ok(()) } diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 7edcf74dec9dc..7a5d24ed90b14 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -24,17 +24,17 @@ macro_rules! sub_pools_with_era { #[test] fn test_setup_works() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(BondedPoolStorage::::count(), 1); - assert_eq!(RewardPoolStorage::::count(), 1); + assert_eq!(BondedPools::::count(), 1); + assert_eq!(RewardPools::::count(), 1); assert_eq!(SubPoolsStorage::::count(), 0); - assert_eq!(DelegatorStorage::::count(), 1); + assert_eq!(Delegators::::count(), 1); assert_eq!( - BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), BondedPool:: { points: 10 } ); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), RewardPool:: { balance: 0, points: 0.into(), @@ -43,7 +43,7 @@ fn test_setup_works() { } ); assert_eq!( - DelegatorStorage::::get(10).unwrap(), + Delegators::::get(10).unwrap(), Delegator:: { pool: PRIMARY_ACCOUNT, points: 10, @@ -354,14 +354,14 @@ mod join { ExtBuilder::default().build_and_execute(|| { // Given Balances::make_free_balance_be(&11, ExistentialDeposit::get() + 2); - assert!(!DelegatorStorage::::contains_key(&11)); + assert!(!Delegators::::contains_key(&11)); // When assert_ok!(Pools::join(Origin::signed(11), 2, PRIMARY_ACCOUNT)); // then assert_eq!( - DelegatorStorage::::get(&11).unwrap(), + Delegators::::get(&11).unwrap(), Delegator:: { pool: PRIMARY_ACCOUNT, points: 2, @@ -370,7 +370,7 @@ mod join { } ); assert_eq!( - BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), BondedPool:: { points: 12 } ); @@ -379,14 +379,14 @@ mod join { StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 6); // And Balances::make_free_balance_be(&12, ExistentialDeposit::get() + 12); - assert!(!DelegatorStorage::::contains_key(&12)); + assert!(!Delegators::::contains_key(&12)); // When assert_ok!(Pools::join(Origin::signed(12), 12, PRIMARY_ACCOUNT)); // Then assert_eq!( - DelegatorStorage::::get(&12).unwrap(), + Delegators::::get(&12).unwrap(), Delegator:: { pool: PRIMARY_ACCOUNT, points: 24, @@ -395,7 +395,7 @@ mod join { } ); assert_eq!( - BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), BondedPool:: { points: 12 + 24 } ); }); @@ -419,7 +419,7 @@ mod join { Error::::OverflowRisk ); - BondedPoolStorage::::insert(123, BondedPool:: { points: 100 }); + BondedPools::::insert(123, BondedPool:: { points: 100 }); // Force the points:balance ratio to 100/10 (so 10) StakingMock::set_bonded_balance(123, 10); assert_noop!(Pools::join(Origin::signed(11), 420, 123), Error::::OverflowRisk); @@ -440,7 +440,7 @@ mod join { Pools::join(Origin::signed(11), 420, 123), Error::::RewardPoolNotFound ); - RewardPoolStorage::::insert( + RewardPools::::insert( 1, RewardPool:: { balance: Zero::zero(), @@ -482,9 +482,9 @@ mod claim_payout { // Then // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool // balance - assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 100)); + assert_eq!(Delegators::::get(10).unwrap(), del(10, 100)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(90, 100 * 100 - 100 * 10, 100) ); assert_eq!(Balances::free_balance(&10), 10); @@ -495,9 +495,9 @@ mod claim_payout { // Then // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance - assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 100)); + assert_eq!(Delegators::::get(40).unwrap(), del(40, 100)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(50, 9_000 - 100 * 40, 100) ); assert_eq!(Balances::free_balance(&40), 40); @@ -508,9 +508,9 @@ mod claim_payout { // Then // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance - assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 100)); + assert_eq!(Delegators::::get(50).unwrap(), del(50, 100)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(0, 0, 100) ); assert_eq!(Balances::free_balance(&50), 50); @@ -524,9 +524,9 @@ mod claim_payout { // Then // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance - assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 150)); + assert_eq!(Delegators::::get(10).unwrap(), del(10, 150)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(45, 5_000 - 50 * 10, 150) ); assert_eq!(Balances::free_balance(&10), 10 + 5); @@ -538,9 +538,9 @@ mod claim_payout { // Then // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool // balance - assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 150)); + assert_eq!(Delegators::::get(40).unwrap(), del(40, 150)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(25, 4_500 - 50 * 40, 150) ); assert_eq!(Balances::free_balance(&40), 40 + 20); @@ -556,9 +556,9 @@ mod claim_payout { // Then // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 // pool balance - assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 200)); + assert_eq!(Delegators::::get(50).unwrap(), del(50, 200)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew( 25, // old pool points + points from new earnings - del points. @@ -577,9 +577,9 @@ mod claim_payout { // Then // We expect a payout of 5 - assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 200)); + assert_eq!(Delegators::::get(10).unwrap(), del(10, 200)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(20, 2_500 - 10 * 50, 200) ); assert_eq!(Balances::free_balance(&10), 15 + 5); @@ -594,9 +594,9 @@ mod claim_payout { // Then // We expect a payout of 40 - assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 600)); + assert_eq!(Delegators::::get(10).unwrap(), del(10, 600)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew( 380, // old pool points + points from new earnings - del points @@ -620,9 +620,9 @@ mod claim_payout { // Then // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool // balance - assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 620)); + assert_eq!(Delegators::::get(10).unwrap(), del(10, 620)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(398, (38_000 + 20 * 100) - 10 * 20, 620) ); assert_eq!(Balances::free_balance(&10), 60 + 2); @@ -634,9 +634,9 @@ mod claim_payout { // Then // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 // pool balance - assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 620)); + assert_eq!(Delegators::::get(40).unwrap(), del(40, 620)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(210, 39_800 - 40 * 470, 620) ); assert_eq!(Balances::free_balance(&40), 60 + 188); @@ -647,9 +647,9 @@ mod claim_payout { // Then // Expect payout of 210: (21,000 / 21,000) * 210 - assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 620)); + assert_eq!(Delegators::::get(50).unwrap(), del(50, 620)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(0, 21_000 - 50 * 420, 620) ); assert_eq!(Balances::free_balance(&50), 100 + 210); @@ -660,9 +660,9 @@ mod claim_payout { #[test] fn calculate_delegator_payout_errors_if_a_delegator_is_unbonding() { ExtBuilder::default().build_and_execute(|| { - let bonded_pool = BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); - let reward_pool = RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); - let mut delegator = DelegatorStorage::::get(10).unwrap(); + let bonded_pool = BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut delegator = Delegators::::get(10).unwrap(); delegator.unbonding_era = Some(0); assert_noop!( @@ -681,9 +681,9 @@ mod claim_payout { // unbonding_era: None, // }; ExtBuilder::default().build_and_execute(|| { - let bonded_pool = BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); - let reward_pool = RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); - let delegator = DelegatorStorage::::get(10).unwrap(); + let bonded_pool = BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let delegator = Delegators::::get(10).unwrap(); // Given no rewards have been earned // When @@ -738,14 +738,14 @@ mod claim_payout { ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - let bonded_pool = BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); - let reward_pool = RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); + let bonded_pool = BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); // Delegator with 10 points - let del_10 = DelegatorStorage::::get(10).unwrap(); + let del_10 = Delegators::::get(10).unwrap(); // Delegator with 40 points - let del_40 = DelegatorStorage::::get(40).unwrap(); + let del_40 = Delegators::::get(40).unwrap(); // Delegator with 50 points - let del_50 = DelegatorStorage::::get(50).unwrap(); + let del_50 = Delegators::::get(50).unwrap(); // Given we have a total of 100 points split among the delegators assert_eq!(del_50.points + del_40.points + del_10.points, 100); @@ -923,7 +923,7 @@ mod claim_payout { ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - let bonded_pool = BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); + let bonded_pool = BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(); // Given the bonded pool has 100 points assert_eq!(bonded_pool.points, 100); @@ -937,16 +937,16 @@ mod claim_payout { // When assert_ok!(Pools::do_reward_payout( 10, - DelegatorStorage::get(10).unwrap(), + Delegators::get(10).unwrap(), &bonded_pool )); // Then // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool // balance - assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 100)); + assert_eq!(Delegators::::get(10).unwrap(), del(10, 100)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(90, 100 * 100 - 100 * 10, 100) ); assert_eq!(Balances::free_balance(&10), 10); @@ -955,15 +955,15 @@ mod claim_payout { // When assert_ok!(Pools::do_reward_payout( 40, - DelegatorStorage::get(40).unwrap(), + Delegators::get(40).unwrap(), &bonded_pool )); // Then // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance - assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 100)); + assert_eq!(Delegators::::get(40).unwrap(), del(40, 100)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(50, 9_000 - 100 * 40, 100) ); assert_eq!(Balances::free_balance(&40), 40); @@ -972,15 +972,15 @@ mod claim_payout { // When assert_ok!(Pools::do_reward_payout( 50, - DelegatorStorage::get(50).unwrap(), + Delegators::get(50).unwrap(), &bonded_pool )); // Then // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance - assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 100)); + assert_eq!(Delegators::::get(50).unwrap(), del(50, 100)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(0, 0, 100) ); assert_eq!(Balances::free_balance(&50), 50); @@ -992,15 +992,15 @@ mod claim_payout { // When assert_ok!(Pools::do_reward_payout( 10, - DelegatorStorage::get(10).unwrap(), + Delegators::get(10).unwrap(), &bonded_pool )); // Then // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance - assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 150)); + assert_eq!(Delegators::::get(10).unwrap(), del(10, 150)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(45, 5_000 - 50 * 10, 150) ); assert_eq!(Balances::free_balance(&10), 10 + 5); @@ -1009,16 +1009,16 @@ mod claim_payout { // When assert_ok!(Pools::do_reward_payout( 40, - DelegatorStorage::get(40).unwrap(), + Delegators::get(40).unwrap(), &bonded_pool )); // Then // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool // balance - assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 150)); + assert_eq!(Delegators::::get(40).unwrap(), del(40, 150)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(25, 4_500 - 50 * 40, 150) ); assert_eq!(Balances::free_balance(&40), 40 + 20); @@ -1031,16 +1031,16 @@ mod claim_payout { // When assert_ok!(Pools::do_reward_payout( 50, - DelegatorStorage::get(50).unwrap(), + Delegators::get(50).unwrap(), &bonded_pool )); // Then // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 // pool balance - assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 200)); + assert_eq!(Delegators::::get(50).unwrap(), del(50, 200)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew( 25, // old pool points + points from new earnings - del points. @@ -1057,15 +1057,15 @@ mod claim_payout { // When assert_ok!(Pools::do_reward_payout( 10, - DelegatorStorage::get(10).unwrap(), + Delegators::get(10).unwrap(), &bonded_pool )); // Then // We expect a payout of 5 - assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 200)); + assert_eq!(Delegators::::get(10).unwrap(), del(10, 200)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(20, 2_500 - 10 * 50, 200) ); assert_eq!(Balances::free_balance(&10), 15 + 5); @@ -1078,15 +1078,15 @@ mod claim_payout { // When assert_ok!(Pools::do_reward_payout( 10, - DelegatorStorage::get(10).unwrap(), + Delegators::get(10).unwrap(), &bonded_pool )); // Then // We expect a payout of 40 - assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 600)); + assert_eq!(Delegators::::get(10).unwrap(), del(10, 600)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew( 380, // old pool points + points from new earnings - del points @@ -1107,16 +1107,16 @@ mod claim_payout { // When assert_ok!(Pools::do_reward_payout( 10, - DelegatorStorage::get(10).unwrap(), + Delegators::get(10).unwrap(), &bonded_pool )); // Then // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool // balance - assert_eq!(DelegatorStorage::::get(10).unwrap(), del(10, 620)); + assert_eq!(Delegators::::get(10).unwrap(), del(10, 620)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(398, (38_000 + 20 * 100) - 10 * 20, 620) ); assert_eq!(Balances::free_balance(&10), 60 + 2); @@ -1125,16 +1125,16 @@ mod claim_payout { // When assert_ok!(Pools::do_reward_payout( 40, - DelegatorStorage::get(40).unwrap(), + Delegators::get(40).unwrap(), &bonded_pool )); // Then // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 // pool balance - assert_eq!(DelegatorStorage::::get(40).unwrap(), del(40, 620)); + assert_eq!(Delegators::::get(40).unwrap(), del(40, 620)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(210, 39_800 - 40 * 470, 620) ); assert_eq!(Balances::free_balance(&40), 60 + 188); @@ -1143,15 +1143,15 @@ mod claim_payout { // When assert_ok!(Pools::do_reward_payout( 50, - DelegatorStorage::get(50).unwrap(), + Delegators::get(50).unwrap(), &bonded_pool )); // Then // Expect payout of 210: (21,000 / 21,000) * 210 - assert_eq!(DelegatorStorage::::get(50).unwrap(), del(50, 620)); + assert_eq!(Delegators::::get(50).unwrap(), del(50, 620)); assert_eq!( - RewardPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(0, 21_000 - 50 * 420, 620) ); assert_eq!(Balances::free_balance(&50), 100 + 210); @@ -1162,8 +1162,8 @@ mod claim_payout { #[test] fn do_reward_payout_errors_correctly() { ExtBuilder::default().build_and_execute(|| { - let bonded_pool = BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(); - let mut delegator = DelegatorStorage::::get(10).unwrap(); + let bonded_pool = BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut delegator = Delegators::::get(10).unwrap(); // The only place this can return an error is with the balance transfer from the // reward account to the delegator, and as far as this comment author can tell this @@ -1198,7 +1198,7 @@ mod unbond { ); assert_eq!( - BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), BondedPool { points: 0 } ); @@ -1225,11 +1225,11 @@ mod unbond { sub_pools_with_era! { 0 => UnbondPool { points: 6, balance: 6 }} ); assert_eq!( - BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), BondedPool { points: 560 } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 94); - assert_eq!(DelegatorStorage::::get(40).unwrap().unbonding_era, Some(0)); + assert_eq!(Delegators::::get(40).unwrap().unbonding_era, Some(0)); assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding // When @@ -1241,11 +1241,11 @@ mod unbond { sub_pools_with_era! { 0 => UnbondPool { points: 7, balance: 7 }} ); assert_eq!( - BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), BondedPool { points: 550 } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 93); - assert_eq!(DelegatorStorage::::get(10).unwrap().unbonding_era, Some(0)); + assert_eq!(Delegators::::get(10).unwrap().unbonding_era, Some(0)); assert_eq!(Balances::free_balance(&10), 10 + 10); // When @@ -1257,11 +1257,11 @@ mod unbond { sub_pools_with_era! { 0 => UnbondPool { points: 100, balance: 100 }} ); assert_eq!( - BondedPoolStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), BondedPool { points: 0 } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); - assert_eq!(DelegatorStorage::::get(550).unwrap().unbonding_era, Some(0)); + assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0)); assert_eq!(Balances::free_balance(&550), 550 + 550); }); } @@ -1314,13 +1314,13 @@ mod unbond { reward_pool_total_earnings: 0, unbonding_era: None, }; - DelegatorStorage::::insert(11, delegator); + Delegators::::insert(11, delegator); assert_noop!(Pools::unbond(Origin::signed(11)), Error::::PoolNotFound); // Add bonded pool to go along with the delegator let bonded_pool = BondedPool { points: 10 }; - BondedPoolStorage::::insert(1, bonded_pool); + BondedPools::::insert(1, bonded_pool); assert_noop!(Pools::unbond(Origin::signed(11)), Error::::RewardPoolNotFound); }); @@ -1391,7 +1391,7 @@ mod withdraw_unbonded { ); assert_eq!(Balances::free_balance(&550), 550 + 297); assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 275 + 40 + 10 - 297); - assert!(!DelegatorStorage::::contains_key(550)); + assert!(!Delegators::::contains_key(550)); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 0)); @@ -1403,7 +1403,7 @@ mod withdraw_unbonded { ); assert_eq!(Balances::free_balance(&40), 40 + 22); assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 28 - 22); - assert!(!DelegatorStorage::::contains_key(40)); + assert!(!Delegators::::contains_key(40)); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 0)); @@ -1415,7 +1415,7 @@ mod withdraw_unbonded { ); assert_eq!(Balances::free_balance(&10), 10 + 6); assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 0); - assert!(!DelegatorStorage::::contains_key(10)); + assert!(!Delegators::::contains_key(10)); }); } @@ -1449,7 +1449,7 @@ mod withdraw_unbonded { ); assert_eq!(Balances::free_balance(&40), 40 + 6); assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 94); - assert!(!DelegatorStorage::::contains_key(40)); + assert!(!Delegators::::contains_key(40)); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 0)); @@ -1461,7 +1461,7 @@ mod withdraw_unbonded { ); assert_eq!(Balances::free_balance(&10), 10 + 1); assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 93); - assert!(!DelegatorStorage::::contains_key(10)); + assert!(!Delegators::::contains_key(10)); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 0)); @@ -1473,7 +1473,7 @@ mod withdraw_unbonded { ); assert_eq!(Balances::free_balance(&550), 550 + 93); assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 0); - assert!(!DelegatorStorage::::contains_key(550)); + assert!(!Delegators::::contains_key(550)); }); } @@ -1491,7 +1491,7 @@ mod withdraw_unbonded { reward_pool_total_earnings: 0, unbonding_era: None, }; - DelegatorStorage::::insert(11, delegator.clone()); + Delegators::::insert(11, delegator.clone()); // The delegator has not called `unbond` assert_noop!( @@ -1501,7 +1501,7 @@ mod withdraw_unbonded { // Simulate calling `unbond` delegator.unbonding_era = Some(0); - DelegatorStorage::::insert(11, delegator.clone()); + Delegators::::insert(11, delegator.clone()); // We are still in the bonding duration assert_noop!( @@ -1532,7 +1532,7 @@ mod withdraw_unbonded { ); // If we error the delegator does not get removed - assert_eq!(DelegatorStorage::::get(&11), Some(delegator)); + assert_eq!(Delegators::::get(&11), Some(delegator)); // and the subpools do not get updated. assert_eq!(SubPoolsStorage::::get(123).unwrap(), sub_pools) }); @@ -1545,42 +1545,41 @@ mod create { #[test] fn create_works() { ExtBuilder::default().build_and_execute(|| { - let bonded_account = 337692978; + let stash = 337692978; - assert!(!BondedPoolStorage::::contains_key(1)); - assert!(!RewardPoolStorage::::contains_key(1)); - assert!(!DelegatorStorage::::contains_key(11)); - assert_eq!(StakingMock::bonded_balance(&bonded_account), None); + assert!(!BondedPools::::contains_key(1)); + assert!(!RewardPools::::contains_key(1)); + assert!(!Delegators::::contains_key(11)); + assert_eq!(StakingMock::bonded_balance(&stash), None); Balances::make_free_balance_be(&11, StakingMock::minimum_bond()); - println!("a"); assert_ok!(Pools::create(Origin::signed(11), vec![], StakingMock::minimum_bond(), 42)); assert_eq!(Balances::free_balance(&11), 0); assert_eq!( - DelegatorStorage::::get(11).unwrap(), + Delegators::::get(11).unwrap(), Delegator { - pool: bonded_account, + pool: stash, points: StakingMock::minimum_bond(), reward_pool_total_earnings: Zero::zero(), unbonding_era: None } ); assert_eq!( - BondedPoolStorage::::get(1708226889).unwrap(), + BondedPools::::get(stash).unwrap(), BondedPool { points: StakingMock::minimum_bond() } ); assert_eq!( - StakingMock::bonded_balance(&bonded_account).unwrap(), + StakingMock::bonded_balance(&stash).unwrap(), StakingMock::minimum_bond() ); assert_eq!( - RewardPoolStorage::::get(1708226889).unwrap(), + RewardPools::::get(stash).unwrap(), RewardPool { balance: Zero::zero(), points: U256::zero(), total_earnings: Zero::zero(), - account_id: 1842460259 + account_id: 2844952004 } ); }); From 0292f11b076afbfa5ea9761f7d71eabeae160f75 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 27 Jan 2022 21:49:43 -0800 Subject: [PATCH 054/299] Test & update `PoolInterface::slash_pool` --- frame/pools/src/lib.rs | 108 ++++++------- frame/pools/src/mock.rs | 3 +- frame/pools/src/tests.rs | 288 +++++++++++++++++++++++++--------- primitives/staking/src/lib.rs | 34 +++- 4 files changed, 297 insertions(+), 136 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 1a04e4dc626db..24a56e033dead 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -58,7 +58,7 @@ use frame_support::{ use scale_info::TypeInfo; use sp_core::U256; use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZeroInput, Zero}; -use sp_staking::{EraIndex, PoolsInterface, StakingInterface}; +use sp_staking::{EraIndex, PoolsInterface, SlashPoolArgs, SlashPoolOut, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div}; #[cfg(test)] @@ -320,6 +320,9 @@ impl SubPools { struct MaxUnbonding(PhantomData); impl Get for MaxUnbonding { fn get() -> u32 { + // TODO: This may be too dangerous in the scenario bonding_duration gets decreased because + // we would no longer be able to decode `SubPoolsWithEra`, which uses `MaxUnbonding` as the + // bound T::StakingInterface::bonding_duration() + T::WithEraWithdrawWindow::get() } } @@ -452,13 +455,9 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; // If a delegator already exists that means they already belong to a pool - ensure!( - !Delegators::::contains_key(&who), - Error::::AccountBelongsToOtherPool - ); + ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); - let mut bonded_pool = - BondedPools::::get(&target).ok_or(Error::::PoolNotFound)?; + let mut bonded_pool = BondedPools::::get(&target).ok_or(Error::::PoolNotFound)?; bonded_pool.ok_to_join_with(&target, amount)?; // The pool should always be created in such a way its in a state to bond extra, but if @@ -528,8 +527,7 @@ pub mod pallet { #[pallet::weight(666)] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let delegator = - Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; let bonded_pool = BondedPools::::get(&delegator.pool).ok_or_else(|| { log!(error, "A bonded pool could not be found, this is a system logic error."); Error::::PoolNotFound @@ -545,8 +543,7 @@ pub mod pallet { #[pallet::weight(666)] pub fn unbond(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let delegator = - Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; let mut bonded_pool = BondedPools::::get(&delegator.pool).ok_or(Error::::PoolNotFound)?; @@ -556,8 +553,7 @@ pub mod pallet { Self::do_reward_payout(who.clone(), delegator, &bonded_pool)?; // Re-fetch the delegator because they where updated by `do_reward_payout`. - let mut delegator = - Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let mut delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPoolsStorage::::get(&delegator.pool).unwrap_or_default(); let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); @@ -607,8 +603,7 @@ pub mod pallet { #[pallet::weight(666)] pub fn withdraw_unbonded(origin: OriginFor, num_slashing_spans: u32) -> DispatchResult { let who = ensure_signed(origin)?; - let delegator = - Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); @@ -865,18 +860,15 @@ impl Pallet { } fn do_slash( - // This would be the nominator account - pool_account: &T::AccountId, - // Value of slash - slash_amount: BalanceOf, - // Era the slash was initially reported - slash_era: EraIndex, - // Era the slash is applied in - apply_era: EraIndex, - // The current active bonded of the account (i.e. `StakingLedger::active`) - active_bonded_balance: BalanceOf, - ) -> Option<(BalanceOf, BTreeMap>)> { - let mut sub_pools = SubPoolsStorage::::get(pool_account).unwrap_or_default(); + SlashPoolArgs { + pool_stash, + slash_amount, + slash_era, + apply_era, + active_bonded, + }: SlashPoolArgs::>, + ) -> Option>> { + let mut sub_pools = SubPoolsStorage::::get(pool_stash).unwrap_or_default(); let affected_range = (slash_era + 1)..=apply_era; @@ -889,27 +881,31 @@ impl Pallet { balance_sum } }); - let total_affected_balance = - active_bonded_balance.saturating_add(unbonding_affected_balance); + + // Note that the balances of the bonded pool and its affected sub-pools will saturated at + // zero if slash_amount > total_affected_balance + let total_affected_balance = active_bonded.saturating_add(unbonding_affected_balance); if total_affected_balance.is_zero() { - return Some((Zero::zero(), Default::default())) + return Some(SlashPoolOut { + new_active_bonded: Zero::zero(), + new_unlocking: Default::default(), + }) } - if slash_amount > total_affected_balance { - // TODO this shouldn't happen as long as MaxBonding pools is greater thant the slash - // defer duration, which it should implicitly be because we expect it be longer then the - // UnbondindDuration. TODO clearly document these assumptions - }; - let unlock_chunk_balances: BTreeMap<_, _> = affected_range + let new_unlocking: BTreeMap<_, _> = affected_range .filter_map(|era| { if let Some(mut unbond_pool) = sub_pools.with_era.get_mut(&era) { - // Equivalent to `(slash_amount / total_affected_balance) * unbond_pool.balance` - let pool_slash_amount = slash_amount - .saturating_mul(unbond_pool.balance) - // We check for zero above - .div(total_affected_balance); - let after_slash_balance = unbond_pool.balance.saturating_sub(pool_slash_amount); + let after_slash_balance = { + // Equivalent to `(slash_amount / total_affected_balance) * + // unbond_pool.balance` + let pool_slash_amount = slash_amount + .saturating_mul(unbond_pool.balance) + // We check for zero above + .div(total_affected_balance); + + unbond_pool.balance.saturating_sub(pool_slash_amount) + }; unbond_pool.balance = after_slash_balance; @@ -920,15 +916,19 @@ impl Pallet { }) .collect(); - SubPoolsStorage::::insert(pool_account, sub_pools); + SubPoolsStorage::::insert(pool_stash, sub_pools); + + // Equivalent to `(slash_amount / total_affected_balance) * active_bonded` + let new_active_bonded = { + let bonded_pool_slash_amount = slash_amount + .saturating_mul(active_bonded) + // We check for zero above + .div(total_affected_balance); - // Equivalent to `(slash_amount / total_affected_balance) * active_bonded_balance` - let slashed_bonded_pool_balance = slash_amount - .saturating_mul(active_bonded_balance) - // We check for zero above - .div(total_affected_balance); + active_bonded.saturating_sub(bonded_pool_slash_amount) + }; - Some((slashed_bonded_pool_balance, unlock_chunk_balances)) + Some(SlashPoolOut { new_active_bonded, new_unlocking }) } } @@ -937,13 +937,9 @@ impl PoolsInterface for Pallet { type Balance = BalanceOf; fn slash_pool( - pool_account: &Self::AccountId, - slash_amount: Self::Balance, - slash_era: EraIndex, - apply_era: EraIndex, - active_bonded: BalanceOf, - ) -> Option<(Self::Balance, BTreeMap)> { - Self::do_slash(pool_account, slash_amount, slash_era, apply_era, active_bonded) + args: SlashPoolArgs, + ) -> Option> { + Self::do_slash(args) } } diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index ec36a890ffb7e..742a1fd73c30e 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -18,6 +18,7 @@ parameter_types! { pub static CanBondExtra: bool = true; pub static CanBond: bool = true; pub static CanNominate: bool = true; + pub static BondingDuration: EraIndex = 3; } pub struct StakingMock; @@ -41,7 +42,7 @@ impl sp_staking::StakingInterface for StakingMock { } fn bonding_duration() -> EraIndex { - 3 + BondingDuration::get() } fn bonded_balance(who: &Self::AccountId) -> Option { diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 7a5d24ed90b14..56ae841f364bc 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1,8 +1,8 @@ use super::*; use crate::mock::{ - Balance, Balances, CanBond, CanBondExtra, CanNominate, CurrentEra, ExistentialDeposit, - ExtBuilder, Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, - UNBONDING_BALANCE_MAP, + Balance, Balances, BondingDuration, CanBond, CanBondExtra, CanNominate, CurrentEra, + ExistentialDeposit, ExtBuilder, Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, + REWARDS_ACCOUNT, UNBONDING_BALANCE_MAP, }; use frame_support::{assert_noop, assert_ok}; @@ -509,10 +509,7 @@ mod claim_payout { // Then // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance assert_eq!(Delegators::::get(50).unwrap(), del(50, 100)); - assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), - rew(0, 0, 100) - ); + assert_eq!(RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(0, 0, 100)); assert_eq!(Balances::free_balance(&50), 50); assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); @@ -935,11 +932,7 @@ mod claim_payout { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 100); // When - assert_ok!(Pools::do_reward_payout( - 10, - Delegators::get(10).unwrap(), - &bonded_pool - )); + assert_ok!(Pools::do_reward_payout(10, Delegators::get(10).unwrap(), &bonded_pool)); // Then // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool @@ -953,11 +946,7 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 90); // When - assert_ok!(Pools::do_reward_payout( - 40, - Delegators::get(40).unwrap(), - &bonded_pool - )); + assert_ok!(Pools::do_reward_payout(40, Delegators::get(40).unwrap(), &bonded_pool)); // Then // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance @@ -970,19 +959,12 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 50); // When - assert_ok!(Pools::do_reward_payout( - 50, - Delegators::get(50).unwrap(), - &bonded_pool - )); + assert_ok!(Pools::do_reward_payout(50, Delegators::get(50).unwrap(), &bonded_pool)); // Then // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance assert_eq!(Delegators::::get(50).unwrap(), del(50, 100)); - assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), - rew(0, 0, 100) - ); + assert_eq!(RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(0, 0, 100)); assert_eq!(Balances::free_balance(&50), 50); assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); @@ -990,11 +972,7 @@ mod claim_payout { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 50); // When - assert_ok!(Pools::do_reward_payout( - 10, - Delegators::get(10).unwrap(), - &bonded_pool - )); + assert_ok!(Pools::do_reward_payout(10, Delegators::get(10).unwrap(), &bonded_pool)); // Then // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance @@ -1007,11 +985,7 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 45); // When - assert_ok!(Pools::do_reward_payout( - 40, - Delegators::get(40).unwrap(), - &bonded_pool - )); + assert_ok!(Pools::do_reward_payout(40, Delegators::get(40).unwrap(), &bonded_pool)); // Then // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool @@ -1029,11 +1003,7 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 75); // When - assert_ok!(Pools::do_reward_payout( - 50, - Delegators::get(50).unwrap(), - &bonded_pool - )); + assert_ok!(Pools::do_reward_payout(50, Delegators::get(50).unwrap(), &bonded_pool)); // Then // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 @@ -1055,11 +1025,7 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 25); // When - assert_ok!(Pools::do_reward_payout( - 10, - Delegators::get(10).unwrap(), - &bonded_pool - )); + assert_ok!(Pools::do_reward_payout(10, Delegators::get(10).unwrap(), &bonded_pool)); // Then // We expect a payout of 5 @@ -1076,11 +1042,7 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 420); // When - assert_ok!(Pools::do_reward_payout( - 10, - Delegators::get(10).unwrap(), - &bonded_pool - )); + assert_ok!(Pools::do_reward_payout(10, Delegators::get(10).unwrap(), &bonded_pool)); // Then // We expect a payout of 40 @@ -1105,11 +1067,7 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 400); // When - assert_ok!(Pools::do_reward_payout( - 10, - Delegators::get(10).unwrap(), - &bonded_pool - )); + assert_ok!(Pools::do_reward_payout(10, Delegators::get(10).unwrap(), &bonded_pool)); // Then // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool @@ -1123,11 +1081,7 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 398); // When - assert_ok!(Pools::do_reward_payout( - 40, - Delegators::get(40).unwrap(), - &bonded_pool - )); + assert_ok!(Pools::do_reward_payout(40, Delegators::get(40).unwrap(), &bonded_pool)); // Then // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 @@ -1141,11 +1095,7 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 210); // When - assert_ok!(Pools::do_reward_payout( - 50, - Delegators::get(50).unwrap(), - &bonded_pool - )); + assert_ok!(Pools::do_reward_payout(50, Delegators::get(50).unwrap(), &bonded_pool)); // Then // Expect payout of 210: (21,000 / 21,000) * 210 @@ -1569,10 +1519,7 @@ mod create { BondedPools::::get(stash).unwrap(), BondedPool { points: StakingMock::minimum_bond() } ); - assert_eq!( - StakingMock::bonded_balance(&stash).unwrap(), - StakingMock::minimum_bond() - ); + assert_eq!(StakingMock::bonded_balance(&stash).unwrap(), StakingMock::minimum_bond()); assert_eq!( RewardPools::::get(stash).unwrap(), RewardPool { @@ -1619,7 +1566,204 @@ mod pools_interface { use super::*; #[test] - fn slash_pool_works() { - ExtBuilder::default().build_and_execute(|| {}); + fn slash_pool_works_in_simple_cases() { + // Slash with no sub pools + ExtBuilder::default().build_and_execute(|| { + // When + let SlashPoolOut { new_active_bonded, new_unlocking } = + Pools::slash_pool(SlashPoolArgs { + pool_stash: &PRIMARY_ACCOUNT, + slash_amount: 9, + slash_era: 0, + apply_era: 3, + active_bonded: 10, + }) + .unwrap(); + + // Then + assert_eq!( + SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), + Default::default() + ); + assert_eq!(new_unlocking, Default::default()); + assert_eq!(new_active_bonded, 1); + + // Slash, some sub pools are in range, some are out + // Same as above, but a slash amount greater than total slashable + }); + + // Slash, but all sub pools are out of range + ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { + // Given + // Unbond in era 0 + assert_ok!(Pools::unbond(Origin::signed(10))); + + // When + let SlashPoolOut { new_active_bonded, new_unlocking } = + Pools::slash_pool(SlashPoolArgs { + pool_stash: &PRIMARY_ACCOUNT, + slash_amount: 9, + // We start slashing unbonding pools in `slash_era + 1` + slash_era: 0, + apply_era: 3, + active_bonded: 100, + }) + .unwrap(); + + // Then + assert_eq!( + SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), + SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { + 0 => UnbondPool { points: 10, balance: 10 } + } + } + ); + assert_eq!(new_unlocking, Default::default()); + assert_eq!(new_active_bonded, 91); + }); + } + + // Some sub pools are in range of the slash while others are not. + #[test] + fn slash_pool_works_in_complex_cases() { + ExtBuilder::default() + .add_delegators(vec![(40, 40), (100, 100), (200, 200), (300, 300), (400, 400)]) + .build_and_execute(|| { + // Make sure no pools get merged into `SubPools::no_era` until era 7. + BondingDuration::set(5); + assert_eq!(MaxUnbonding::::get(), 7); + + assert_ok!(Pools::unbond(Origin::signed(10))); + + CurrentEra::set(1); + assert_ok!(Pools::unbond(Origin::signed(40))); + + CurrentEra::set(3); + assert_ok!(Pools::unbond(Origin::signed(100))); + + CurrentEra::set(5); + assert_ok!(Pools::unbond(Origin::signed(200))); + + CurrentEra::set(6); + assert_ok!(Pools::unbond(Origin::signed(300))); + + // Given + assert_eq!( + SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), + SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { + 0 => UnbondPool { points: 10, balance: 10 }, + 1 => UnbondPool { points: 40, balance: 40 }, + 3 => UnbondPool { points: 100, balance: 100 }, + 5 => UnbondPool { points: 200, balance: 200 }, + 6 => UnbondPool { points: 300, balance: 300 }, + } + } + ); + + // When + let SlashPoolOut { new_active_bonded, new_unlocking } = + Pools::slash_pool(SlashPoolArgs { + pool_stash: &PRIMARY_ACCOUNT, + slash_amount: (40 + 100 + 200 + 400) / 2, + slash_era: 0, + apply_era: 5, + active_bonded: 400, + }) + .unwrap(); + + // Then + assert_eq!( + SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), + SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { + 0 => UnbondPool { points: 10, balance: 10 }, + 1 => UnbondPool { points: 40, balance: 40 / 2 }, + 3 => UnbondPool { points: 100, balance: 100 / 2}, + 5 => UnbondPool { points: 200, balance: 200 / 2}, + 6 => UnbondPool { points: 300, balance: 300 }, + } + } + ); + let expected_new_unlocking: BTreeMap<_, _> = + [(1, 40 / 2), (3, 100 / 2), (5, 200 / 2)].into_iter().collect(); + assert_eq!(new_unlocking, expected_new_unlocking); + assert_eq!(new_active_bonded, 400 / 2); + }); + } + + // Same as above, but the slash amount is greater than the slash-able balance of the pool. + #[test] + fn pool_slash_works_with_slash_amount_greater_than_slashable() { + ExtBuilder::default() + .add_delegators(vec![(40, 40), (100, 100), (200, 200), (300, 300), (400, 400)]) + .build_and_execute(|| { + // Make sure no pools get merged into `SubPools::no_era` until era 7. + BondingDuration::set(5); + assert_eq!(MaxUnbonding::::get(), 7); + + assert_ok!(Pools::unbond(Origin::signed(10))); + + CurrentEra::set(1); + assert_ok!(Pools::unbond(Origin::signed(40))); + + CurrentEra::set(3); + assert_ok!(Pools::unbond(Origin::signed(100))); + + CurrentEra::set(5); + assert_ok!(Pools::unbond(Origin::signed(200))); + + CurrentEra::set(6); + assert_ok!(Pools::unbond(Origin::signed(300))); + + // Given + assert_eq!( + SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), + SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { + 0 => UnbondPool { points: 10, balance: 10 }, + 1 => UnbondPool { points: 40, balance: 40 }, + 3 => UnbondPool { points: 100, balance: 100 }, + 5 => UnbondPool { points: 200, balance: 200 }, + 6 => UnbondPool { points: 300, balance: 300 }, + } + } + ); + + // When + let SlashPoolOut { new_active_bonded, new_unlocking } = + Pools::slash_pool(SlashPoolArgs { + pool_stash: &PRIMARY_ACCOUNT, + slash_amount: 40 + 100 + 200 + 400 + 10, + slash_era: 0, + apply_era: 5, + active_bonded: 400, + }) + .unwrap(); + + // Then + assert_eq!( + SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), + SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { + 0 => UnbondPool { points: 10, balance: 10 }, + 1 => UnbondPool { points: 40, balance: 0 }, + 3 => UnbondPool { points: 100, balance: 0 }, + 5 => UnbondPool { points: 200, balance: 0 }, + 6 => UnbondPool { points: 300, balance: 300 }, + } + } + ); + let expected_new_unlocking: BTreeMap<_, _> = + [(1, 0), (3, 0), (5, 0)].into_iter().collect(); + assert_eq!(new_unlocking, expected_new_unlocking); + assert_eq!(new_active_bonded, 0); + }); } } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 341cc8c41340c..4b2d5e530b0c3 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -30,6 +30,29 @@ pub type SessionIndex = u32; /// Counter for the number of eras that have passed. pub type EraIndex = u32; +/// Arguments for [`PoolsInterface::slash_pool`]. +pub struct SlashPoolArgs<'a, AccountId, Balance> { + /// _Stash_ account of the pool + pub pool_stash: &'a AccountId, + /// The amount to slash + pub slash_amount: Balance, + /// Era the slash happened in + pub slash_era: EraIndex, + /// Era the slash is applied in + pub apply_era: EraIndex, + /// The current active bonded of the account (i.e. `StakingLedger::active`) + pub active_bonded: Balance, +} + +/// Output for [`PoolsInterface::slash_pool`]. +pub struct SlashPoolOut { + /// The new active bonded balance of the stash with the proportional slash amounted subtracted. + pub new_active_bonded: Balance, + /// A map from era of unlocking chunks to their new balance with the proportional slash amount + /// subtracted. + pub new_unlocking: BTreeMap, +} + pub trait PoolsInterface { type AccountId; type Balance; @@ -51,12 +74,8 @@ pub trait PoolsInterface { /// balance. This should apply the updated balances to the pools and return the updated balances /// to the caller (presumably pallet-staking) so they can do the corresponding updates. fn slash_pool( - account_id: &Self::AccountId, - slash_amount: Self::Balance, - slash_era: EraIndex, - active_era: EraIndex, - active_bonded: Self::Balance, - ) -> Option<(Self::Balance, BTreeMap)>; + args: SlashPoolArgs, + ) -> Option>; } /// Trait for communication with the staking pallet. @@ -74,7 +93,8 @@ pub trait StakingInterface { /// nominator (e.g. in the bags-list of polkadot) fn minimum_bond() -> Self::Balance; - /// Number of eras that staked funds must remain bonded for. + /// Number of eras that staked funds must remain bonded for. NOTE: it is assumed that this is + /// always strictly greater than the slash deffer duration. fn bonding_duration() -> EraIndex; /// The current era for the staking system. From 732c30b75ee7b97d787fe4ff3447150eef7bfa9b Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 28 Jan 2022 14:25:06 -0800 Subject: [PATCH 055/299] Add module docs --- frame/pools/src/lib.rs | 240 +++++++++++++++++++++++++++++++++------ frame/pools/src/tests.rs | 2 +- 2 files changed, 209 insertions(+), 33 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 24a56e033dead..09b41c325416d 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -1,50 +1,226 @@ -//! Delegation pools for nominating in `pallet-staking`. +//! # Delegation pools for nominating //! -//! The delegation pool abstraction is concretely composed of: +//! This pallet allows delegators to delegate their stake to nominating pools, each of which acts as +//! a nominator and nominates validators on their behalf. //! -//! * bonded pool: This pool represents the actively staked funds ... -//! * rewards pool: The rewards earned by actively staked funds. Delegator can withdraw rewards once -//! * sub pools: This a group of pools where we have a set of pools organized by era -//! (`SubPools::with_era`) and one pool that is not associated with an era (`SubPools.no_era`). -//! Once a `with_era` pool is older then `current_era - MaxUnbonding`, its points and balance get -//! merged into the `no_era` pool. +//! ## Design //! -//! # Design goals -//! - Maintain integrity of slashing events in terms of penalizing those backing the validator that -//! equivocated. -//! - Maximizing scalability -//! - Lazy: no parts of the design require any hooks or intervalled upkeep by the chain. All -//! actions can be triggered for a delegator via an extrinsic. +//! _Notes_: this section uses pseudo code to explain general design and does not neccesarily +//! reflect the actual implementation. Additionally, a strong knowledge of `pallet-staking`'s api is +//! assumed) //! -//! # Joining +//! The delegation pool abstraction is composed of: //! -//! # Claiming rewards +//! bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and +//! [`BondedPools`] +//! * reward pool: Tracks rewards earned by actively staked funds. See [`RewardPool`] and +//! [`RewardPooks`]. +//! * unbonding sub pools: This a collection of pools at different phases of the unbonding +//! lifecycle. See [`SubPools`] and [`SubPoolsStorage`]. +//! * delegators: Accounts that are members of pools. See [`Delegator`] and [`Delegators`]. +//! In order to maintain scalability, all operations are independent of the number of delegators. To +//! do this, we store delegation specific information local to the delegator while the pool data +//! structures have bounded datum . //! -//! # Unbonding and withdrawing +//! ### Design goals //! -//! # Slashing +//! * Maintain integrity of slashing events, correctly penalizing delegators that where the pool +//! while it was backing a validator that got slashed. +//! * Maximize scalability in terms of delegator count. +//! +//! +//! ### Bonded pool +//! +//! A bonded pool nominates with its total balance, excluding that which has been withdrawn for +//! unbonding. The total points of a bonded pool are always equal to the sum of points of the +//! delegation members. A bonded pool tracks its points and reads its bonded balance. +//! +//! When a delegator joins a pool, `amount_transferred` is transferred from the delegators account +//! to the bonded pools account. Then the pool calls `bond_extra(amount_transferred)` and issues new +//! points which are tracked by the delegator and added to the bonded pools points. +//! +//! When the pool already has some balance, we want the value of a point before the transfer to equal +//! the value of a point after the transfer. So, when a delegator joins a bonded pool with a given +//! `amount_transferred`, we maintain the ratio of bonded balance to points such that: +//! +//! ``` +//! balance_after_transfer / total_points_after_transfer == balance_before_transfer / total_points_before_transfer; +//! ``` +//! +//! To achieve this, we issue points based on the following: +//! +//! ``` +//! new_points_issued = (total_points_before_transfer / balance_before_transfer) * amount_transferred; +//! ``` +//! +//! For new bonded pools we can set the points issued per balance arbitrarily. In this +//! implementation we use a 1 points to 1 balance ratio for pool creation (see +//! [`POINTS_TO_BALANCE_INIT_RATIO`]). +//! +//! **Relevant extrinsics:** +//! +//! * [`Call::create`] +//! * [`Call::join`] +//! +//! ### Reward pool +//! +//! When a pool is first bonded it sets up an arbirtrary account as its reward destination. To track +//! staking rewards we track how the balance of this reward account changes. +//! +//! The reward pool needs to store: +//! +//! * The pool balance at the time of the last payout: `reward_pool.balance` +//! * The total earnings ever at the time of the last payout: `reward_pool.total_earnings` +//! * The total points in the pool at the time of the last payout: `reward_pool.points` +//! +//! And the delegator needs to store: +//! +//! * The total payouts at the time of the last payout by that delegator: +//! `delegator.reward_pool_total_earnings` +//! +//! Before the first reward claim is initiated for a pool, all the above variables are set to zero. +//! +//! When a delegator initiates a claim, the following happens: +//! +//! 1) Compute the reward pool's total points and the delegator's virtual points in the reward pool +//! * We first compute `current_total_earnings` (`current_balance` is the free balance of the +//! reward pool at the beginning of these operations.) ``` current_total_earnings = +//! current_balance - reward_pool.balance + pool.total_earnings; ``` +//! * Then we compute the `current_points`. Every balance unit that was added to the reward pool +//! since last time recorded means that we increase the `pool.points` by +//! `bonding_pool.total_points`. In other words, for every unit of balance that has been +//! earned by the reward pool, we inflate the reward pool points by `bonded_pool.points`. In +//! effect this allows each, single unit of balance (e.g. planck) to be divvied up pro-rata +//! among delegators based on points. ``` new_earnings = current_total_earnings - +//! reward_pool.total_earnings; current_points = reward_pool.points + bonding_pool.points * +//! new_earnings; ``` +//! * Finally, we compute `delegator_virtual_points`: the product of the delegator's points in +//! the bonding pool and the total inflow of balance units since the last time the delegator +//! claimed rewards ``` new_earnings_since_last_claim = current_total_earnings - +//! delegator.reward_pool_total_earnings; delegator_virtual_points = delegator.points * +//! new_earnings_since_last_claim; ``` +//! 2) Compute the `delegator_payout`: +//! ``` +//! delegator_pool_point_ratio = delegator_virtual_points / current_points; +//! delegator_payout = current_balance * delegator_pool_point_ratio; +//! ``` +//! 3) Transfer `delegator_payout` to the delegator +//! 4) For the delegator set: +//! ``` +//! delegator.reward_pool_total_earnings = current_total_earnings +//! ``` +//! 5) For the pool set: +//! ``` +//! reward_pool.points = current_points - delegator_virtual_points; +//! reward_pool.balance = current_balance - delegator_payout; +//! reward_pool.total_earnings = current_total_earnings; +//! ``` +//! _Note_: One short coming of this design is that new joiners can claim rewards for the era after +//! they join in even though their funds did not contribute to the pools vote weight. When a +//! delegator joins, they set the field `reward_pool_total_earnings` equal to the `total_earnings` +//! of the reward pool at that point in time. At best the reward pool has the rewards up through the +//! previous era. If the delegator joins prior to the election snapshot they will benefit from the +//! rewards for the current era despite not contributing to the pool's vote weight. If they join +//! after the election snapshot is taken they will benefit from the rewards of the next _2_ eras +//! because their vote weight will not be counted until the election snapshot in current era + 1. +//! +//! **Relevant extrinsics:** +//! +//! * [`Call::claim_payout`] +//! +//! ### Unbonding sub pools +//! +//! When a delegator unbonds, their balance is unbonded in the bonded pool's account and tracked in +//! an unbonding pool associated with the active era. If no such pool exists, one is created. To +//! track which unbonding sub pool a delegator belongs too, the delegator tracks their +//! `unbonding_era`. +//! +//! When a delegator initiates unbonding we calculate their claim on the bonded pool +//! (`balance_to_unbond`) as: +//! +//! ``` +//! balance_to_unbond = (bonded_pool.balance / bonded_pool.points) * delegator.points; +//! ``` +//! If this is the first transfer into the unbonding pool we can issue an arbitrary amount of points +//! per balance. In this implementation we initialize unbonding pools with a 1 point to 1 balance +//! ratio (see [`POINTS_TO_BALANCE_INIT_RATIO`]). Otherwise, the unbonding pools hold the same +//! points to balance ratio properties as the bonded pool, so we issue the delegator points in the +//! unbonding pool based on +//! +//! ``` +//! new_points_issued = (points_before_transfer / balance_before_transfer) * balance_to_unbond; +//! ``` +//! +//! For scalability, we maintain a bound on the number of unbonding sub pools (see [`MaxUnboning`]). +//! Once we reach this bound the oldest unbonding pool is merged into the new unbonded pool (see +//! `no_era` field in [`SubPools`]). In other words, a unbonding pool is removed once its older than +//! `current_era - MaxUnbonding`. We merge an unbonding pool into the unbonded pool with +//! +//! ``` +//! unbounded_pool.balance = unbounded_pool.balance + unbonding_pool.balance; +//! unbounded_pool.points = unbounded_pool.points + unbonding_pool.points; +//! ``` +//! +//! This scheme "averages" out the points value in the unbonded pool. +//! +//! Once the delgators `unbonding_era` is older than `current_era - +//! [sp_staking::StakingInterface::bonding_duration]`, they can can cash their points out of the +//! corresponding unbonding pool. If their `unbonding_era` is older than the oldest unbonding pool +//! they cash their points out of the unbonded pool. +//! +//! **Relevant extrinsics +//! +//! * [`Call::unbond`] +//! * [`Call::with_unbonded`] +//! +//! ### Slashing +//! +//! We distribute slashes evenly across the bonded pool and the unbonding pools from slash era+1 +//! through the slash apply era. +//! +//! To compute and execute the slashes: +//! +//! 1) Sum up the balances of the bonded pool and the unbonding pools in range `slash_era + +//! 1..=apply_era` and store that in `total_balance_affected`. 2) Compute `slash_ratio` as +//! `slash_amount / total_balance_affected`. 3) Compute `bonded_pool_balance_after_slash` as `(1- +//! slash_ratio) * bonded_pool_balance`. 4) For all `unbonding_pool` in range `slash_era + +//! 1..=apply_era` set their balance to `(1 - slash_ratio) * unbonding_pool_balance`. +//! +//! We need to slash the relevant unbonding pools to ensure all nominators whom where in the pool +//! while it was backing a validator that equivocated are punished. Without these measures a +//! nominator could unbond right after a validator equivocated with no consequences. +//! +//! This strategy is unfair to delegators who joined after the slash, because they get slashed as +//! well, but spares delegators who unbond. The latter is much more important for security: if a +//! pool's validators are attacking the network, we want their delegators to unbond fast! Avoiding +//! slashes gives them an incentive to do that if validators get repeatedly slashed. +//! +//! To be fair to joiners, we would also need joining pools, which are actively staking, in addition +//! to the unbonding pools. For maintenance simplicity these are not implemented. +//! +//! ### Limitations +//! +//! * Delegators cannot vote with their staked funds because they are transferred into the pools +//! account. In the future this can be overcome by allowing the delegators to vote with their +//! bonded funds via vote splitting. +//! * Delegators cannot quickly transfer to another pool if they do no like nominations, instead +//! they must wait for the unbonding duration. //! //! # Pool creation and upkeep //! //! TBD - possible options: //! * Pools can be created by anyone but nominations can never be updated -//! * Pools can be created by anyone and the creator can update nominations +//! * Pools can be created by anyone and the creator can update the targets //! * Pools are created by governance and governance can update the targets //! ... Other ideas -//! * pools can have different roles assigned - admin, nominator, destroyer, etc -//! For example: Governance can create a pool and be the admin, then they could -//! assign a nominator like 1KV -//! -//! # Negatives -//! - no voting -//! - .. +//! * pools can have different roles assigned: creator (puts deposit down, cannot remove deposit +//! until pool is empty), admin (can control who is nominator, destroyer and can do +//! nominate/destroy), nominator (can adjust targets), destroyer (can initiate destroy), etc //! -//! # Future features -//! - allow voting via vote splitting +//! # Runtime builder warnings //! -//! RUNTIME BUILDER WARNINGS -//! - watch out for overflow of `RewardPoints` and `BalanceOf` types. Consider the chains total -//! issuance, staking reward rate, and burn rate. +//! * watch out for overflow of [`RewardPoints`] and [`BalanceOf`] types. Consider things like the +//! chains total issuance, staking reward rate, and burn rate. #![cfg_attr(not(feature = "std"), no_std)] @@ -779,7 +955,7 @@ impl Pallet { // The new points that will be added to the pool. For every unit of balance that has // been earned by the reward pool, we inflate the reward pool points by - // `bonded_pool.total_points`. In effect this allows each, single unit of balance (e.g. + // `bonded_pool.points`. In effect this allows each, single unit of balance (e.g. // plank) to be divvied up pro-rata among delegators based on points. let new_points = T::BalanceToU256::convert(bonded_pool.points).saturating_mul(new_earnings); diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 56ae841f364bc..4731426d97c12 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1694,7 +1694,7 @@ mod pools_interface { assert_eq!(new_unlocking, expected_new_unlocking); assert_eq!(new_active_bonded, 400 / 2); }); - } + } // Same as above, but the slash amount is greater than the slash-able balance of the pool. #[test] From 469c0ce297a3c0c53a39050da315dbe025da815c Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 28 Jan 2022 15:12:56 -0800 Subject: [PATCH 056/299] Cross out some TODOs --- frame/pools/src/lib.rs | 68 +++++++++++-------------- frame/pools/src/mock.rs | 10 ++++ frame/pools/src/tests.rs | 95 ++++++++++++++++++----------------- primitives/staking/src/lib.rs | 4 +- 4 files changed, 89 insertions(+), 88 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 09b41c325416d..dd24180966998 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -5,9 +5,9 @@ //! //! ## Design //! -//! _Notes_: this section uses pseudo code to explain general design and does not neccesarily -//! reflect the actual implementation. Additionally, a strong knowledge of `pallet-staking`'s api is -//! assumed) +//! _Notes_: this section uses pseudo code to explain general design and does not necessarily +//! reflect the exact implementation. Additionally, a strong knowledge of `pallet-staking`'s api is +//! assumed. //! //! The delegation pool abstraction is composed of: //! @@ -24,11 +24,10 @@ //! //! ### Design goals //! -//! * Maintain integrity of slashing events, correctly penalizing delegators that where the pool -//! while it was backing a validator that got slashed. +//! * Maintain integrity of slashing events, sufficiently penalizing delegators that where in the +//! pool while it was backing a validator that got slashed. //! * Maximize scalability in terms of delegator count. //! -//! //! ### Bonded pool //! //! A bonded pool nominates with its total balance, excluding that which has been withdrawn for @@ -39,18 +38,18 @@ //! to the bonded pools account. Then the pool calls `bond_extra(amount_transferred)` and issues new //! points which are tracked by the delegator and added to the bonded pools points. //! -//! When the pool already has some balance, we want the value of a point before the transfer to equal -//! the value of a point after the transfer. So, when a delegator joins a bonded pool with a given -//! `amount_transferred`, we maintain the ratio of bonded balance to points such that: +//! When the pool already has some balance, we want the value of a point before the transfer to +//! equal the value of a point after the transfer. So, when a delegator joins a bonded pool with a +//! given `amount_transferred`, we maintain the ratio of bonded balance to points such that: //! //! ``` -//! balance_after_transfer / total_points_after_transfer == balance_before_transfer / total_points_before_transfer; +//! balance_after_transfer / points_after_transfer == balance_before_transfer / points_before_transfer; //! ``` //! //! To achieve this, we issue points based on the following: //! //! ``` -//! new_points_issued = (total_points_before_transfer / balance_before_transfer) * amount_transferred; +//! points_issued = (points_before_transfer / balance_before_transfer) * amount_transferred; //! ``` //! //! For new bonded pools we can set the points issued per balance arbitrarily. In this @@ -64,7 +63,7 @@ //! //! ### Reward pool //! -//! When a pool is first bonded it sets up an arbirtrary account as its reward destination. To track +//! When a pool is first bonded it sets up an arbitrary account as its reward destination. To track //! staking rewards we track how the balance of this reward account changes. //! //! The reward pool needs to store: @@ -107,7 +106,7 @@ //! 3) Transfer `delegator_payout` to the delegator //! 4) For the delegator set: //! ``` -//! delegator.reward_pool_total_earnings = current_total_earnings +//! delegator.reward_pool_total_earnings = current_total_earnings; //! ``` //! 5) For the pool set: //! ``` @@ -115,6 +114,7 @@ //! reward_pool.balance = current_balance - delegator_payout; //! reward_pool.total_earnings = current_total_earnings; //! ``` +//! //! _Note_: One short coming of this design is that new joiners can claim rewards for the era after //! they join in even though their funds did not contribute to the pools vote weight. When a //! delegator joins, they set the field `reward_pool_total_earnings` equal to the `total_earnings` @@ -321,7 +321,6 @@ pub struct Delegator { points: BalanceOf, /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. /// Assuming no massive burning events, we expect this value to always be below total issuance. - // TODO: ^ double check the above is an OK assumption /// This value lines up with the `RewardPool::total_earnings` after a delegator claims a /// payout. reward_pool_total_earnings: BalanceOf, @@ -673,13 +672,12 @@ pub mod pallet { Delegator:: { pool: target.clone(), points: new_points, - // TODO double check that this is ok. // At best the reward pool has the rewards up through the previous era. If the // delegator joins prior to the snapshot they will benefit from the rewards of - // the current era despite not contributing to the pool's vote weight. If they + // the active era despite not contributing to the pool's vote weight. If they // join after the snapshot is taken they will benefit from the rewards of the - // next *2* eras because their vote weight will not be counted until the - // snapshot in current era + 1. + // next 2 eras because their vote weight will not be counted until the + // snapshot in active era + 1. reward_pool_total_earnings: reward_pool.total_earnings, unbonding_era: None, }, @@ -717,7 +715,7 @@ pub mod pallet { /// A bonded delegator can use this to unbond _all_ funds from the pool. /// In order to withdraw the funds, the delegator must wait #[pallet::weight(666)] - pub fn unbond(origin: OriginFor) -> DispatchResult { + pub fn unbond(origin: OriginFor, num_slashing_spans: u32) -> DispatchResult { let who = ensure_signed(origin)?; let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; let mut bonded_pool = @@ -741,7 +739,12 @@ pub mod pallet { // to unbond so we have the correct points for the balance:share ratio. bonded_pool.points = bonded_pool.points.saturating_sub(delegator.points); - // TODO: call withdraw unbonded to try and minimize unbonding chunks + // Call withdraw unbonded to minimize unlocking chunks. If this is not done then we + // would have to rely on delegators calling `withdraw_unbonded` in order to clear + // unlocking chunks. This is a catch 22 for delegators who have not yet unbonded + // because the pool needs to call `withdraw_unbonded` so they can `unbond`, but they + // must call `unbond` prior to being able to call `withdraw_unbonded`. + T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; // Unbond in the actual underlying pool T::StakingInterface::unbond(delegator.pool.clone(), balance_to_unbond)?; @@ -807,9 +810,7 @@ pub mod pallet { balance_to_unbond }; - if T::Currency::free_balance(&delegator.pool) < balance_to_unbond { - T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; - } + T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; T::Currency::transfer( &delegator.pool, @@ -1064,12 +1065,12 @@ impl Pallet { if total_affected_balance.is_zero() { return Some(SlashPoolOut { - new_active_bonded: Zero::zero(), - new_unlocking: Default::default(), + slashed_bonded: Zero::zero(), + slashed_unlocking: Default::default(), }) } - let new_unlocking: BTreeMap<_, _> = affected_range + let slashed_unlocking: BTreeMap<_, _> = affected_range .filter_map(|era| { if let Some(mut unbond_pool) = sub_pools.with_era.get_mut(&era) { let after_slash_balance = { @@ -1095,7 +1096,7 @@ impl Pallet { SubPoolsStorage::::insert(pool_stash, sub_pools); // Equivalent to `(slash_amount / total_affected_balance) * active_bonded` - let new_active_bonded = { + let slashed_bonded = { let bonded_pool_slash_amount = slash_amount .saturating_mul(active_bonded) // We check for zero above @@ -1104,7 +1105,7 @@ impl Pallet { active_bonded.saturating_sub(bonded_pool_slash_amount) }; - Some(SlashPoolOut { new_active_bonded, new_unlocking }) + Some(SlashPoolOut { slashed_bonded, slashed_unlocking }) } } @@ -1118,14 +1119,3 @@ impl PoolsInterface for Pallet { Self::do_slash(args) } } - -// TODO -// - -// - tests -// - force pool creation -// - rebond_rewards -// - pool update targets -// - pool block - don't allow new joiners (or bond_extra) -// - pool begin destroy - unbond the entire pool balance -// - pool end destroy - once all subpools are empty delete everything from storage -// - force pool delete? diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 742a1fd73c30e..76acb1b15d9c9 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -19,6 +19,7 @@ parameter_types! { pub static CanBond: bool = true; pub static CanNominate: bool = true; pub static BondingDuration: EraIndex = 3; + pub static DisableWithdrawUnbonded: bool = false; } pub struct StakingMock; @@ -60,14 +61,23 @@ impl sp_staking::StakingInterface for StakingMock { fn unbond(who: Self::AccountId, amount: Self::Balance) -> DispatchResult { BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(&who).unwrap() -= amount); + println!("unbond amount: {:?}", amount); UNBONDING_BALANCE_MAP .with(|m| *m.borrow_mut().entry(who).or_insert(Self::Balance::zero()) += amount); Ok(()) } fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result { + if DisableWithdrawUnbonded::get() { + // We have a naive impl - it will always withdraw whatever is unbonding regardless of era + // So sometimes we may want to disable it to simulate calls in eras where there is nothing + // to completely unlock. + return Ok(1) + } + let maybe_new_free = UNBONDING_BALANCE_MAP.with(|m| m.borrow_mut().remove(&who)); if let Some(new_free) = maybe_new_free { + println!("new_free: {:?}", new_free); assert_ok!(Balances::mutate_account(&who, |a| a.free += new_free)); } Ok(100) diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 4731426d97c12..68c4ec04002ff 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -2,17 +2,10 @@ use super::*; use crate::mock::{ Balance, Balances, BondingDuration, CanBond, CanBondExtra, CanNominate, CurrentEra, ExistentialDeposit, ExtBuilder, Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, - REWARDS_ACCOUNT, UNBONDING_BALANCE_MAP, + REWARDS_ACCOUNT, UNBONDING_BALANCE_MAP, DisableWithdrawUnbonded }; use frame_support::{assert_noop, assert_ok}; -// -// - get pallet-pools to compile and pass test -// - implement staking impl of the delegator pools interface -// - factor out can_* -> pre_execution_checks -// - test `slash_pool` -// - incorporate returned weight from staking calls - macro_rules! sub_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ use sp_std::iter::{Iterator, IntoIterator}; @@ -1140,7 +1133,7 @@ mod unbond { #[test] fn unbond_pool_of_1_works() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond(Origin::signed(10), 0)); assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, @@ -1167,7 +1160,7 @@ mod unbond { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 600); // When - assert_ok!(Pools::unbond(Origin::signed(40))); + assert_ok!(Pools::unbond(Origin::signed(40), 0)); // Then assert_eq!( @@ -1183,7 +1176,7 @@ mod unbond { assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding // When - assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond(Origin::signed(10), 0)); // Then assert_eq!( @@ -1199,7 +1192,7 @@ mod unbond { assert_eq!(Balances::free_balance(&10), 10 + 10); // When - assert_ok!(Pools::unbond(Origin::signed(550))); + assert_ok!(Pools::unbond(Origin::signed(550), 0)); // Then assert_eq!( @@ -1236,7 +1229,7 @@ mod unbond { let current_era = 1 + MaxUnbonding::::get(); CurrentEra::set(current_era); - assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond(Origin::signed(10), 0)); // Then assert_eq!( @@ -1255,7 +1248,7 @@ mod unbond { #[test] fn unbond_errors_correctly() { ExtBuilder::default().build_and_execute(|| { - assert_noop!(Pools::unbond(Origin::signed(11)), Error::::DelegatorNotFound); + assert_noop!(Pools::unbond(Origin::signed(11), 0), Error::::DelegatorNotFound); // Add the delegator let delegator = Delegator { @@ -1266,13 +1259,16 @@ mod unbond { }; Delegators::::insert(11, delegator); - assert_noop!(Pools::unbond(Origin::signed(11)), Error::::PoolNotFound); + assert_noop!(Pools::unbond(Origin::signed(11), 0), Error::::PoolNotFound); // Add bonded pool to go along with the delegator let bonded_pool = BondedPool { points: 10 }; BondedPools::::insert(1, bonded_pool); - assert_noop!(Pools::unbond(Origin::signed(11)), Error::::RewardPoolNotFound); + assert_noop!( + Pools::unbond(Origin::signed(11), 0), + Error::::RewardPoolNotFound + ); }); } } @@ -1288,14 +1284,14 @@ mod withdraw_unbonded { // Given Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 0); - assert_ok!(Pools::unbond(Origin::signed(10))); - assert_ok!(Pools::unbond(Origin::signed(40))); + assert_ok!(Pools::unbond(Origin::signed(10), 0)); + assert_ok!(Pools::unbond(Origin::signed(40), 0)); let mut current_era = 1; CurrentEra::set(current_era); // In a new era, unbond 550 - assert_ok!(Pools::unbond(Origin::signed(550))); + assert_ok!(Pools::unbond(Origin::signed(550), 0)); // Simulate a slash to the pool with_era(current_era) let mut sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(); @@ -1377,9 +1373,14 @@ mod withdraw_unbonded { // Given StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); // slash bonded balance Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 0); - assert_ok!(Pools::unbond(Origin::signed(10))); - assert_ok!(Pools::unbond(Origin::signed(40))); - assert_ok!(Pools::unbond(Origin::signed(550))); + + // Disable withdraw unbonded so unbond calls do not withdraw funds unbonded immediately prior. + DisableWithdrawUnbonded::set(true); + assert_ok!(Pools::unbond(Origin::signed(10), 0)); + assert_ok!(Pools::unbond(Origin::signed(40), 0)); + assert_ok!(Pools::unbond(Origin::signed(550), 0)); + DisableWithdrawUnbonded::set(false); + SubPoolsStorage::::insert( PRIMARY_ACCOUNT, SubPools { @@ -1570,7 +1571,7 @@ mod pools_interface { // Slash with no sub pools ExtBuilder::default().build_and_execute(|| { // When - let SlashPoolOut { new_active_bonded, new_unlocking } = + let SlashPoolOut { slashed_bonded, slashed_unlocking } = Pools::slash_pool(SlashPoolArgs { pool_stash: &PRIMARY_ACCOUNT, slash_amount: 9, @@ -1585,8 +1586,8 @@ mod pools_interface { SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), Default::default() ); - assert_eq!(new_unlocking, Default::default()); - assert_eq!(new_active_bonded, 1); + assert_eq!(slashed_unlocking, Default::default()); + assert_eq!(slashed_bonded, 1); // Slash, some sub pools are in range, some are out // Same as above, but a slash amount greater than total slashable @@ -1596,10 +1597,10 @@ mod pools_interface { ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given // Unbond in era 0 - assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond(Origin::signed(10), 0)); // When - let SlashPoolOut { new_active_bonded, new_unlocking } = + let SlashPoolOut { slashed_bonded, slashed_unlocking } = Pools::slash_pool(SlashPoolArgs { pool_stash: &PRIMARY_ACCOUNT, slash_amount: 9, @@ -1620,8 +1621,8 @@ mod pools_interface { } } ); - assert_eq!(new_unlocking, Default::default()); - assert_eq!(new_active_bonded, 91); + assert_eq!(slashed_unlocking, Default::default()); + assert_eq!(slashed_bonded, 91); }); } @@ -1635,19 +1636,19 @@ mod pools_interface { BondingDuration::set(5); assert_eq!(MaxUnbonding::::get(), 7); - assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond(Origin::signed(10), 0)); CurrentEra::set(1); - assert_ok!(Pools::unbond(Origin::signed(40))); + assert_ok!(Pools::unbond(Origin::signed(40), 0)); CurrentEra::set(3); - assert_ok!(Pools::unbond(Origin::signed(100))); + assert_ok!(Pools::unbond(Origin::signed(100), 0)); CurrentEra::set(5); - assert_ok!(Pools::unbond(Origin::signed(200))); + assert_ok!(Pools::unbond(Origin::signed(200), 0)); CurrentEra::set(6); - assert_ok!(Pools::unbond(Origin::signed(300))); + assert_ok!(Pools::unbond(Origin::signed(300), 0)); // Given assert_eq!( @@ -1665,7 +1666,7 @@ mod pools_interface { ); // When - let SlashPoolOut { new_active_bonded, new_unlocking } = + let SlashPoolOut { slashed_bonded, slashed_unlocking } = Pools::slash_pool(SlashPoolArgs { pool_stash: &PRIMARY_ACCOUNT, slash_amount: (40 + 100 + 200 + 400) / 2, @@ -1689,10 +1690,10 @@ mod pools_interface { } } ); - let expected_new_unlocking: BTreeMap<_, _> = + let expected_slashed_unlocking: BTreeMap<_, _> = [(1, 40 / 2), (3, 100 / 2), (5, 200 / 2)].into_iter().collect(); - assert_eq!(new_unlocking, expected_new_unlocking); - assert_eq!(new_active_bonded, 400 / 2); + assert_eq!(slashed_unlocking, expected_slashed_unlocking); + assert_eq!(slashed_bonded, 400 / 2); }); } @@ -1706,19 +1707,19 @@ mod pools_interface { BondingDuration::set(5); assert_eq!(MaxUnbonding::::get(), 7); - assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond(Origin::signed(10), 0)); CurrentEra::set(1); - assert_ok!(Pools::unbond(Origin::signed(40))); + assert_ok!(Pools::unbond(Origin::signed(40), 0)); CurrentEra::set(3); - assert_ok!(Pools::unbond(Origin::signed(100))); + assert_ok!(Pools::unbond(Origin::signed(100), 0)); CurrentEra::set(5); - assert_ok!(Pools::unbond(Origin::signed(200))); + assert_ok!(Pools::unbond(Origin::signed(200), 0)); CurrentEra::set(6); - assert_ok!(Pools::unbond(Origin::signed(300))); + assert_ok!(Pools::unbond(Origin::signed(300), 0)); // Given assert_eq!( @@ -1736,7 +1737,7 @@ mod pools_interface { ); // When - let SlashPoolOut { new_active_bonded, new_unlocking } = + let SlashPoolOut { slashed_bonded, slashed_unlocking } = Pools::slash_pool(SlashPoolArgs { pool_stash: &PRIMARY_ACCOUNT, slash_amount: 40 + 100 + 200 + 400 + 10, @@ -1760,10 +1761,10 @@ mod pools_interface { } } ); - let expected_new_unlocking: BTreeMap<_, _> = + let expected_slashed_unlocking: BTreeMap<_, _> = [(1, 0), (3, 0), (5, 0)].into_iter().collect(); - assert_eq!(new_unlocking, expected_new_unlocking); - assert_eq!(new_active_bonded, 0); + assert_eq!(slashed_unlocking, expected_slashed_unlocking); + assert_eq!(slashed_bonded, 0); }); } } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 4b2d5e530b0c3..6d19b4584569b 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -47,10 +47,10 @@ pub struct SlashPoolArgs<'a, AccountId, Balance> { /// Output for [`PoolsInterface::slash_pool`]. pub struct SlashPoolOut { /// The new active bonded balance of the stash with the proportional slash amounted subtracted. - pub new_active_bonded: Balance, + pub slashed_bonded: Balance, /// A map from era of unlocking chunks to their new balance with the proportional slash amount /// subtracted. - pub new_unlocking: BTreeMap, + pub slashed_unlocking: BTreeMap, } pub trait PoolsInterface { From 53529d82e888f25e965402f2963740e69e87a5b9 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sat, 29 Jan 2022 12:10:49 -0800 Subject: [PATCH 057/299] Update some comments --- frame/pools/src/lib.rs | 15 ++++++++++++++- frame/pools/src/mock.rs | 6 ++---- frame/pools/src/tests.rs | 32 ++++++++------------------------ 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index dd24180966998..ec745059789f0 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -224,6 +224,19 @@ #![cfg_attr(not(feature = "std"), no_std)] +// TODO: +// - make withdraw unbonded permissions-less and remove withdraw unbonded call from unbond +// - make claiming rewards permissionless +// - creation +// - CreateOrigin: the type of origin that can create a pool - can be set with governance call +// - creator: account that cannont unbond until there are no other pool members (essentially deposit) +// - kicker: can kick (force unbond) delegators and block new delegators +// - nominator: can set targets +// - admin: can change kicker, nominator, and make another account admin. +// - checks for number of pools when creating pools (param for max pools, pool creation origin) +// - post checks that rewardpool::count == bondedpool::count. delegators >= bondedpool::count, subpools::count <= bondedpools + + use frame_support::{ ensure, pallet_prelude::*, @@ -639,7 +652,7 @@ pub mod pallet { // the active balance is slashed below the minimum bonded or the account cannot be // found, we exit early. if !T::StakingInterface::can_bond_extra(&target, amount) { - return Err(Error::::StakingError.into()) + Err(Error::::StakingError)?; } // We don't actually care about writing the reward pool, we just need its diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 76acb1b15d9c9..714ff9ec563eb 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -6,9 +6,9 @@ use frame_system::RawOrigin; pub type AccountId = u32; pub type Balance = u128; -/// _Stash_ of the pool that gets created by the ExtBuilder +/// _Stash_ of the pool that gets created by the [`ExtBuilder`]. pub const PRIMARY_ACCOUNT: u32 = 1708226889; -/// Reward destination of the pool that gets created by the ExtBuilder +/// Reward destination of the pool that gets created by the [`ExtBuilder`]. pub const REWARDS_ACCOUNT: u32 = 1842460259; parameter_types! { @@ -61,7 +61,6 @@ impl sp_staking::StakingInterface for StakingMock { fn unbond(who: Self::AccountId, amount: Self::Balance) -> DispatchResult { BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(&who).unwrap() -= amount); - println!("unbond amount: {:?}", amount); UNBONDING_BALANCE_MAP .with(|m| *m.borrow_mut().entry(who).or_insert(Self::Balance::zero()) += amount); Ok(()) @@ -77,7 +76,6 @@ impl sp_staking::StakingInterface for StakingMock { let maybe_new_free = UNBONDING_BALANCE_MAP.with(|m| m.borrow_mut().remove(&who)); if let Some(new_free) = maybe_new_free { - println!("new_free: {:?}", new_free); assert_ok!(Balances::mutate_account(&who, |a| a.free += new_free)); } Ok(100) diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 68c4ec04002ff..b61cd1192d5d8 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -49,6 +49,7 @@ fn test_setup_works() { #[test] fn exercise_delegator_life_cycle() { + todo!() // create pool // join pool // claim rewards @@ -170,30 +171,13 @@ mod bonded_pool { } mod reward_pool { - // use super::*; - - // #[test] - // fn update_total_earnings_and_balance_works() { - // ExtBuilder::default().build_and_execute(|| { - // let pool = RewardPool { - // account_id: 123, - // balance: 0, - // total_earnings: 0, - // points: 0 - // }; - - // Balances::make_free_balance_be(100); - // let pool = pool.update_total_earnings_and_balance(); - // assert_eq!( - // pool.total_earnings, 100 - // ); - // assert_eq!( - // pool.balance, 100 - // ); - - // let pool = pool.update_total_earnings_and_balance(); - // }); - // } + use super::*; + + #[test] + fn update_total_earnings_and_balance_works() { + let reward_pool = RewardPool:: + todo!() + } } mod unbond_pool { From 48ca7b8b284fc52f08e82714aa5f3dbc47e95adc Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Wed, 2 Feb 2022 11:09:46 -0800 Subject: [PATCH 058/299] Update frame/pools/Cargo.toml Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/pools/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/pools/Cargo.toml b/frame/pools/Cargo.toml index 86ef3b381d32b..7131312843bac 100644 --- a/frame/pools/Cargo.toml +++ b/frame/pools/Cargo.toml @@ -40,4 +40,4 @@ std = [ "frame-support/std", "frame-system/std", "sp-arithmetic/std", -] \ No newline at end of file +] From 6f07f5b88a12fb586f68fdebbf888bc0ba1a5953 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Thu, 3 Feb 2022 15:35:00 -0800 Subject: [PATCH 059/299] Update frame/pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/pools/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 896d4e507b105..fb424680854fe 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -18,6 +18,7 @@ //! * unbonding sub pools: This a collection of pools at different phases of the unbonding //! lifecycle. See [`SubPools`] and [`SubPoolsStorage`]. //! * delegators: Accounts that are members of pools. See [`Delegator`] and [`Delegators`]. +//! //! In order to maintain scalability, all operations are independent of the number of delegators. To //! do this, we store delegation specific information local to the delegator while the pool data //! structures have bounded datum . From fb261a9bce33c78e1aa6ff817cb372da3e75354c Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Thu, 3 Feb 2022 15:35:55 -0800 Subject: [PATCH 060/299] Update frame/pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/pools/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index fb424680854fe..2766f5c68383d 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -292,7 +292,6 @@ fn points_to_issue( new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { // The pool was totally slashed. - // This is the equivalent of `(current_points / 1) * new_funds`. new_funds.saturating_mul(current_points) }, From 81ea1f7336975426194c90b78eb3f06ed7070384 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Thu, 3 Feb 2022 15:51:28 -0800 Subject: [PATCH 061/299] Apply suggestions from code review Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 2766f5c68383d..291d59d78f014 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -11,7 +11,7 @@ //! //! The delegation pool abstraction is composed of: //! -//! bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and +//! * bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and //! [`BondedPools`] //! * reward pool: Tracks rewards earned by actively staked funds. See [`RewardPool`] and //! [`RewardPooks`]. From 4c1a5511afc7bff8bee3ef6abebad6cb2abea7a0 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 12:14:29 -0800 Subject: [PATCH 062/299] Some minor doc updates; --- frame/pools/src/lib.rs | 14 +++++++------- frame/staking/src/lib.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 291d59d78f014..95cea365f0c56 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -18,7 +18,7 @@ //! * unbonding sub pools: This a collection of pools at different phases of the unbonding //! lifecycle. See [`SubPools`] and [`SubPoolsStorage`]. //! * delegators: Accounts that are members of pools. See [`Delegator`] and [`Delegators`]. -//! +//! //! In order to maintain scalability, all operations are independent of the number of delegators. To //! do this, we store delegation specific information local to the delegator while the pool data //! structures have bounded datum . @@ -849,12 +849,12 @@ pub mod pallet { Ok(()) } - // Create a pool. - // - // * `targets`: _Stash_ addresses of the validators to nominate - // * `amount`: Balance to delegate to the pool. Must meet the minimum bond. - // * `index`: Disambiguation index for seeding account generation. Likely only useful when - // creating multiple pools in the same extrinsic. + /// Create a pool. + /// + /// * `targets`: _Stash_ addresses of the validators to nominate + /// * `amount`: Balance to delegate to the pool. Must meet the minimum bond. + /// * `index`: Disambiguation index for seeding account generation. Likely only useful when + /// creating multiple pools in the same extrinsic. #[pallet::weight(666)] pub fn create( origin: OriginFor, diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 3c195761d1bc0..7d01f01cef98a 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -540,7 +540,7 @@ where slash_era: EraIndex, active_era: EraIndex, ) -> Balance { - use sp_staking::{SlashPoolOut, SlashPoolArgs}; + use sp_staking::{SlashPoolArgs, SlashPoolOut}; if let Some(SlashPoolOut { slashed_bonded, slashed_unlocking }) = P::slash_pool(SlashPoolArgs { pool_stash: &self.stash, From 35b24bea76d9eea7c444f34fc3a267b88714b38f Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 12:46:17 -0800 Subject: [PATCH 063/299] Remove bounded btreemap deref mut --- frame/pools/src/lib.rs | 17 +++++++++++++++-- frame/support/src/storage/bounded_btree_map.rs | 11 +---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 95cea365f0c56..ddd39ccacd2af 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -501,6 +501,20 @@ impl SubPools { self } + + /// Get the unbond pool for `era`. If one does not exist a default entry will be inserted. + /// + /// The caller must ensure that the `SubPools::with_era` has room for 1 more entry. Calling + /// [`SubPools::maybe_merge_pools`] with the current era should the sub pools are in an ok state + /// to call this method. + fn unchecked_with_era_get_or_make(&mut self, era: EraIndex) -> &mut UnbondPool { + if !self.with_era.contains_key(&era) { + self.with_era.try_insert(era, UnbondPool::default()) + .expect("caller has checked pre-conditions. qed."); + } + + self.with_era.get_mut(&era).expect("entry inserted on the line above. qed.") + } } /// The maximum amount of eras an unbonding pool can exist prior to being merged with the @@ -523,7 +537,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] - #[pallet::generate_storage_info] pub struct Pallet(_); #[pallet::config] @@ -771,7 +784,7 @@ pub mod pallet { // does not yet exist. { let mut unbond_pool = - sub_pools.with_era.entry(current_era).or_insert_with(|| UnbondPool::default()); + sub_pools.unchecked_with_era_get_or_make(current_era); let points_to_issue = unbond_pool.points_to_issue(balance_to_unbond); unbond_pool.points = unbond_pool.points.saturating_add(points_to_issue); unbond_pool.balance = unbond_pool.balance.saturating_add(balance_to_unbond); diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index dcc0ec7c2fb17..a504f39a705c8 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -27,7 +27,7 @@ use sp_std::{ collections::btree_map::BTreeMap, convert::TryFrom, marker::PhantomData, - ops::{Deref, DerefMut}, + ops::{Deref}, }; /// A bounded map based on a B-Tree. @@ -274,15 +274,6 @@ where } } -impl DerefMut for BoundedBTreeMap -where - K: Ord, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - impl AsRef> for BoundedBTreeMap where K: Ord, From 7d963dc86f932141428adbf946433c98f69bf105 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 16:51:01 -0800 Subject: [PATCH 064/299] Remove DerefMut for BoundedBTreeMap' --- frame/pools/src/lib.rs | 6 +++--- frame/pools/src/tests.rs | 13 +++++++------ frame/support/src/storage/bounded_btree_map.rs | 7 ++----- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index ddd39ccacd2af..a34b350f0b6cc 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -509,7 +509,8 @@ impl SubPools { /// to call this method. fn unchecked_with_era_get_or_make(&mut self, era: EraIndex) -> &mut UnbondPool { if !self.with_era.contains_key(&era) { - self.with_era.try_insert(era, UnbondPool::default()) + self.with_era + .try_insert(era, UnbondPool::default()) .expect("caller has checked pre-conditions. qed."); } @@ -783,8 +784,7 @@ pub mod pallet { // unbonded funds. Note that we lazily create the unbond pool if it // does not yet exist. { - let mut unbond_pool = - sub_pools.unchecked_with_era_get_or_make(current_era); + let mut unbond_pool = sub_pools.unchecked_with_era_get_or_make(current_era); let points_to_issue = unbond_pool.points_to_issue(balance_to_unbond); unbond_pool.points = unbond_pool.points.saturating_add(points_to_issue); unbond_pool.balance = unbond_pool.balance.saturating_add(balance_to_unbond); diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index d80901cf55dc8..2865f6a4538fe 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -284,22 +284,23 @@ mod sub_pools { assert_eq!(sub_pool_1, sub_pool_0); // When `current_era == MaxUnbonding`, - let mut sub_pool_1 = sub_pool_1.maybe_merge_pools(4); + let sub_pool_1 = sub_pool_1.maybe_merge_pools(4); // Then it exits early without modifications assert_eq!(sub_pool_1, sub_pool_0); - // Given we have entries for era 0..=5 - sub_pool_1.with_era.insert(5, UnbondPool::::new(50, 50)); - sub_pool_0.with_era.insert(5, UnbondPool::::new(50, 50)); - // When `current_era - MaxUnbonding == 0`, - let sub_pool_1 = sub_pool_1.maybe_merge_pools(5); + let mut sub_pool_1 = sub_pool_1.maybe_merge_pools(5); // Then era 0 is merged into the `no_era` pool sub_pool_0.no_era = sub_pool_0.with_era.remove(&0).unwrap(); assert_eq!(sub_pool_1, sub_pool_0); + // Given we have entries for era 1..=5 + sub_pool_1.with_era.try_insert(5, UnbondPool::::new(50, 50)).unwrap(); + sub_pool_0.with_era.try_insert(5, UnbondPool::::new(50, 50)).unwrap(); + + // When `current_era - MaxUnbonding == 1` let sub_pool_2 = sub_pool_1.maybe_merge_pools(6); let era_1_pool = sub_pool_0.with_era.remove(&1).unwrap(); diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index a504f39a705c8..ed132adac657e 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -23,11 +23,8 @@ use crate::{ }; use codec::{Decode, Encode, MaxEncodedLen}; use sp_std::{ - borrow::Borrow, - collections::btree_map::BTreeMap, - convert::TryFrom, - marker::PhantomData, - ops::{Deref}, + borrow::Borrow, collections::btree_map::BTreeMap, convert::TryFrom, marker::PhantomData, + ops::Deref, }; /// A bounded map based on a B-Tree. From 4a67640166a4092717ee8f34a26794d1187d2bf6 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 17:52:35 -0800 Subject: [PATCH 065/299] Add account id to bonded pool --- frame/pools/src/lib.rs | 48 +++++++++++++++++++++++++++------------- frame/pools/src/tests.rs | 1 - 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index a34b350f0b6cc..e41601e989315 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -14,7 +14,7 @@ //! * bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and //! [`BondedPools`] //! * reward pool: Tracks rewards earned by actively staked funds. See [`RewardPool`] and -//! [`RewardPooks`]. +//! [`RewardPools`]. //! * unbonding sub pools: This a collection of pools at different phases of the unbonding //! lifecycle. See [`SubPools`] and [`SubPoolsStorage`]. //! * delegators: Accounts that are members of pools. See [`Delegator`] and [`Delegators`]. @@ -342,15 +342,28 @@ pub struct Delegator { unbonding_era: Option, } -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] +// #[derive(Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] -#[codec(mel_bound(T: Config))] -#[scale_info(skip_type_params(T))] +// #[codec(mel_bound(T: Config))] +// #[scale_info(skip_type_params(T))] pub struct BondedPool { points: BalanceOf, + account: T::AccountId, } impl BondedPool { + /// Get [`Self`] from storage. Returns `None` if no entry for `pool_account` exists. + pub fn get(pool_account: &T::AccountId) -> Option { + BondedPoolPoints::::try_get(pool_account) + .ok() + .map(|points| Self { points, account: pool_account.clone() }) + } + + /// Insert [`Self`] into storage. + pub fn insert(Self { account, points }: Self) { + BondedPoolPoints::::insert(account, points); + } + /// Get the amount of points to issue for some new funds that will be bonded in the pool. fn points_to_issue(&self, stash: &T::AccountId, new_funds: BalanceOf) -> BalanceOf { let bonded_balance = T::StakingInterface::bonded_balance(stash).unwrap_or(Zero::zero()); @@ -582,10 +595,11 @@ pub mod pallet { #[pallet::storage] pub(crate) type PoolIds = CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId>; - /// Bonded pools. Keyed by the pool's _Stash_/_Controller_. + /// Points for the bonded pools. Keyed by the pool's account. To get or insert a pool see + /// [`BondedPool::get`] and [`BondedPool::insert`] #[pallet::storage] - pub(crate) type BondedPools = - CountedStorageMap<_, Twox64Concat, T::AccountId, BondedPool>; + pub(crate) type BondedPoolPoints = + CountedStorageMap<_, Twox64Concat, T::AccountId, BalanceOf>; /// Reward pools. This is where there rewards for each pool accumulate. When a delegators payout /// is claimed, the balance comes out fo the reward pool. Keyed by the bonded pools @@ -660,7 +674,9 @@ pub mod pallet { // If a delegator already exists that means they already belong to a pool ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); - let mut bonded_pool = BondedPools::::get(&target).ok_or(Error::::PoolNotFound)?; + // let mut bonded_pool = + // BondedPools::::get(&target).ok_or(Error::::PoolNotFound)?; + let mut bonded_pool = BondedPool::::get(&target).ok_or(Error::::PoolNotFound)?; bonded_pool.ok_to_join_with(&target, amount)?; // The pool should always be created in such a way its in a state to bond extra, but if @@ -710,7 +726,7 @@ pub mod pallet { unbonding_era: None, }, ); - BondedPools::insert(&target, bonded_pool); + BondedPool::insert(bonded_pool); Self::deposit_event(Event::::Joined { delegator: who, @@ -730,7 +746,7 @@ pub mod pallet { pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; - let bonded_pool = BondedPools::::get(&delegator.pool).ok_or_else(|| { + let bonded_pool = BondedPool::::get(&delegator.pool).ok_or_else(|| { log!(error, "A bonded pool could not be found, this is a system logic error."); Error::::PoolNotFound })?; @@ -746,8 +762,10 @@ pub mod pallet { pub fn unbond(origin: OriginFor, num_slashing_spans: u32) -> DispatchResult { let who = ensure_signed(origin)?; let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + // let mut bonded_pool = + // BondedPools::::get(&delegator.pool).ok_or(Error::::PoolNotFound)?; let mut bonded_pool = - BondedPools::::get(&delegator.pool).ok_or(Error::::PoolNotFound)?; + BondedPool::::get(&delegator.pool).ok_or(Error::::PoolNotFound)?; // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the bonded pool and thus they can no longer claim their payouts. @@ -799,7 +817,7 @@ pub mod pallet { }); // Now that we know everything has worked write the items to storage. - BondedPools::insert(&delegator.pool, bonded_pool); + BondedPool::insert(bonded_pool); SubPoolsStorage::insert(&delegator.pool, sub_pools); Delegators::insert(who, delegator); @@ -881,12 +899,12 @@ pub mod pallet { let (stash, reward_dest) = Self::create_accounts(index); - ensure!(!BondedPools::::contains_key(&stash), Error::::IdInUse); + ensure!(!BondedPoolPoints::::contains_key(&stash), Error::::IdInUse); T::StakingInterface::bond_checks(&stash, &stash, amount, &reward_dest)?; let (stash, targets) = T::StakingInterface::nominate_checks(&stash, targets)?; - let mut bonded_pool = BondedPool:: { points: Zero::zero() }; + let mut bonded_pool = BondedPool:: { points: Zero::zero(), account: stash.clone() }; // We must calculate the points to issue *before* we bond who's funds, else // points:balance ratio will be wrong. @@ -918,7 +936,7 @@ pub mod pallet { unbonding_era: None, }, ); - BondedPools::::insert(stash.clone(), bonded_pool); + BondedPool::::insert(bonded_pool); RewardPools::::insert( stash, RewardPool:: { diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 2865f6a4538fe..3fcbce28abe26 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -300,7 +300,6 @@ mod sub_pools { sub_pool_1.with_era.try_insert(5, UnbondPool::::new(50, 50)).unwrap(); sub_pool_0.with_era.try_insert(5, UnbondPool::::new(50, 50)).unwrap(); - // When `current_era - MaxUnbonding == 1` let sub_pool_2 = sub_pool_1.maybe_merge_pools(6); let era_1_pool = sub_pool_0.with_era.remove(&1).unwrap(); From 1fc884b3d5680c07f3c628f03cae08bec8cd06ba Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 18:09:54 -0800 Subject: [PATCH 066/299] Fix tests for Add account id to bonded pool --- frame/pools/src/lib.rs | 24 ++++----- frame/pools/src/tests.rs | 111 +++++++++++++++++++-------------------- 2 files changed, 63 insertions(+), 72 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index e41601e989315..1ebe58345c02a 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -342,7 +342,7 @@ pub struct Delegator { unbonding_era: Option, } -// #[derive(Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] +#[derive(RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] // #[codec(mel_bound(T: Config))] // #[scale_info(skip_type_params(T))] @@ -365,28 +365,26 @@ impl BondedPool { } /// Get the amount of points to issue for some new funds that will be bonded in the pool. - fn points_to_issue(&self, stash: &T::AccountId, new_funds: BalanceOf) -> BalanceOf { - let bonded_balance = T::StakingInterface::bonded_balance(stash).unwrap_or(Zero::zero()); + fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { + let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); points_to_issue::(bonded_balance, self.points, new_funds) } // Get the amount of balance to unbond from the pool based on a delegator's points of the pool. fn balance_to_unbond( &self, - stash: &T::AccountId, delegator_points: BalanceOf, ) -> BalanceOf { - let bonded_balance = T::StakingInterface::bonded_balance(stash).unwrap_or(Zero::zero()); + let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); balance_to_unbond::(bonded_balance, self.points, delegator_points) } // Check that the pool can accept a member with `new_funds`. fn ok_to_join_with( &self, - stash: &T::AccountId, new_funds: BalanceOf, ) -> Result<(), DispatchError> { - let bonded_balance = T::StakingInterface::bonded_balance(stash).unwrap_or(Zero::zero()); + let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); let points_to_balance_ratio_floor = self @@ -677,7 +675,7 @@ pub mod pallet { // let mut bonded_pool = // BondedPools::::get(&target).ok_or(Error::::PoolNotFound)?; let mut bonded_pool = BondedPool::::get(&target).ok_or(Error::::PoolNotFound)?; - bonded_pool.ok_to_join_with(&target, amount)?; + bonded_pool.ok_to_join_with(amount)?; // The pool should always be created in such a way its in a state to bond extra, but if // the active balance is slashed below the minimum bonded or the account cannot be @@ -706,7 +704,7 @@ pub mod pallet { let exact_amount_to_bond = new_free_balance.saturating_sub(old_free_balance); // We must calculate the points to issue *before* we bond `who`'s funds, else the // points:balance ratio will be wrong. - let new_points = bonded_pool.points_to_issue(&target, exact_amount_to_bond); + let new_points = bonded_pool.points_to_issue(exact_amount_to_bond); bonded_pool.points = bonded_pool.points.saturating_add(new_points); T::StakingInterface::bond_extra(target.clone(), exact_amount_to_bond)?; @@ -779,7 +777,7 @@ pub mod pallet { let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); let balance_to_unbond = - bonded_pool.balance_to_unbond(&delegator.pool, delegator.points); + bonded_pool.balance_to_unbond(delegator.points); // Update the bonded pool. Note that we must do this *after* calculating the balance // to unbond so we have the correct points for the balance:share ratio. @@ -837,7 +835,6 @@ pub mod pallet { let mut sub_pools = SubPoolsStorage::::get(&delegator.pool).ok_or(Error::::SubPoolsNotFound)?; - let balance_to_unbond = if let Some(pool) = sub_pools.with_era.get_mut(&unbonding_era) { let balance_to_unbond = pool.balance_to_unbond(delegator.points); pool.points = pool.points.saturating_sub(delegator.points); @@ -894,13 +891,10 @@ pub mod pallet { index: u16, ) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(amount >= T::StakingInterface::minimum_bond(), Error::::MinimumBondNotMet); let (stash, reward_dest) = Self::create_accounts(index); - ensure!(!BondedPoolPoints::::contains_key(&stash), Error::::IdInUse); - T::StakingInterface::bond_checks(&stash, &stash, amount, &reward_dest)?; let (stash, targets) = T::StakingInterface::nominate_checks(&stash, targets)?; @@ -908,7 +902,7 @@ pub mod pallet { // We must calculate the points to issue *before* we bond who's funds, else // points:balance ratio will be wrong. - let points_to_issue = bonded_pool.points_to_issue(&stash, amount); + let points_to_issue = bonded_pool.points_to_issue(amount); bonded_pool.points = points_to_issue; T::Currency::transfer(&who, &stash, amount, ExistenceRequirement::AllowDeath)?; diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 3fcbce28abe26..bac01902fc3bf 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -17,14 +17,14 @@ macro_rules! sub_pools_with_era { #[test] fn test_setup_works() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(BondedPools::::count(), 1); + assert_eq!(BondedPoolPoints::::count(), 1); assert_eq!(RewardPools::::count(), 1); assert_eq!(SubPoolsStorage::::count(), 0); assert_eq!(Delegators::::count(), 1); assert_eq!( - BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), - BondedPool:: { points: 10 } + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool:: { points: 10, account: PRIMARY_ACCOUNT } ); assert_eq!( RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), @@ -61,111 +61,111 @@ mod bonded_pool { use super::*; #[test] fn points_to_issue_works() { - let mut bonded_pool = BondedPool:: { points: 100 }; + let mut bonded_pool = BondedPool:: { points: 100, account: 123 }; // 1 points : 1 balance ratio StakingMock::set_bonded_balance(123, 100); - assert_eq!(bonded_pool.points_to_issue(&123, 10), 10); - assert_eq!(bonded_pool.points_to_issue(&123, 0), 0); + assert_eq!(bonded_pool.points_to_issue(10), 10); + assert_eq!(bonded_pool.points_to_issue(0), 0); // 2 points : 1 balance ratio StakingMock::set_bonded_balance(123, 50); - assert_eq!(bonded_pool.points_to_issue(&123, 10), 20); + assert_eq!(bonded_pool.points_to_issue(10), 20); // 1 points : 2 balance ratio StakingMock::set_bonded_balance(123, 100); bonded_pool.points = 50; - assert_eq!(bonded_pool.points_to_issue(&123, 10), 5); + assert_eq!(bonded_pool.points_to_issue(10), 5); // 100 points : 0 balance ratio StakingMock::set_bonded_balance(123, 0); bonded_pool.points = 100; - assert_eq!(bonded_pool.points_to_issue(&123, 10), 100 * 10); + assert_eq!(bonded_pool.points_to_issue(10), 100 * 10); // 0 points : 100 balance StakingMock::set_bonded_balance(123, 100); bonded_pool.points = 100; - assert_eq!(bonded_pool.points_to_issue(&123, 10), 10); + assert_eq!(bonded_pool.points_to_issue(10), 10); // 10 points : 3 balance ratio StakingMock::set_bonded_balance(123, 30); - assert_eq!(bonded_pool.points_to_issue(&123, 10), 33); + assert_eq!(bonded_pool.points_to_issue(10), 33); // 2 points : 3 balance ratio StakingMock::set_bonded_balance(123, 300); bonded_pool.points = 200; - assert_eq!(bonded_pool.points_to_issue(&123, 10), 6); + assert_eq!(bonded_pool.points_to_issue(10), 6); // 4 points : 9 balance ratio StakingMock::set_bonded_balance(123, 900); bonded_pool.points = 400; - assert_eq!(bonded_pool.points_to_issue(&123, 90), 40); + assert_eq!(bonded_pool.points_to_issue(90), 40); } #[test] fn balance_to_unbond_works() { // 1 balance : 1 points ratio - let mut bonded_pool = BondedPool:: { points: 100 }; + let mut bonded_pool = BondedPool:: { points: 100, account: 123 }; StakingMock::set_bonded_balance(123, 100); - assert_eq!(bonded_pool.balance_to_unbond(&123, 10), 10); - assert_eq!(bonded_pool.balance_to_unbond(&123, 0), 0); + assert_eq!(bonded_pool.balance_to_unbond(10), 10); + assert_eq!(bonded_pool.balance_to_unbond(0), 0); // 2 balance : 1 points ratio bonded_pool.points = 50; - assert_eq!(bonded_pool.balance_to_unbond(&123, 10), 20); + assert_eq!(bonded_pool.balance_to_unbond(10), 20); // 100 balance : 0 points ratio StakingMock::set_bonded_balance(123, 0); bonded_pool.points = 0; - assert_eq!(bonded_pool.balance_to_unbond(&123, 10), 0); + assert_eq!(bonded_pool.balance_to_unbond(10), 0); // 0 balance : 100 points ratio StakingMock::set_bonded_balance(123, 0); bonded_pool.points = 100; - assert_eq!(bonded_pool.balance_to_unbond(&123, 10), 0); + assert_eq!(bonded_pool.balance_to_unbond(10), 0); // 10 balance : 3 points ratio StakingMock::set_bonded_balance(123, 100); bonded_pool.points = 30; - assert_eq!(bonded_pool.balance_to_unbond(&123, 10), 33); + assert_eq!(bonded_pool.balance_to_unbond(10), 33); // 2 balance : 3 points ratio StakingMock::set_bonded_balance(123, 200); bonded_pool.points = 300; - assert_eq!(bonded_pool.balance_to_unbond(&123, 10), 6); + assert_eq!(bonded_pool.balance_to_unbond(10), 6); // 4 balance : 9 points ratio StakingMock::set_bonded_balance(123, 400); bonded_pool.points = 900; - assert_eq!(bonded_pool.balance_to_unbond(&123, 90), 40); + assert_eq!(bonded_pool.balance_to_unbond(90), 40); } #[test] fn ok_to_join_with_works() { ExtBuilder::default().build_and_execute(|| { - let pool = BondedPool:: { points: 100 }; + let pool = BondedPool:: { points: 100, account: 123 }; // Simulate a 100% slashed pool StakingMock::set_bonded_balance(123, 0); - assert_noop!(pool.ok_to_join_with(&123, 100), Error::::OverflowRisk); + assert_noop!(pool.ok_to_join_with(100), Error::::OverflowRisk); // Simulate a 89% StakingMock::set_bonded_balance(123, 11); - assert_ok!(pool.ok_to_join_with(&123, 100)); + assert_ok!(pool.ok_to_join_with(100)); // Simulate a 90% slashed pool StakingMock::set_bonded_balance(123, 10); - assert_noop!(pool.ok_to_join_with(&123, 100), Error::::OverflowRisk); + assert_noop!(pool.ok_to_join_with(100), Error::::OverflowRisk); let bonded = 100; StakingMock::set_bonded_balance(123, bonded); // New bonded balance would be over 1/10th of Balance type assert_noop!( - pool.ok_to_join_with(&123, Balance::MAX / 10 - bonded), + pool.ok_to_join_with(Balance::MAX / 10 - bonded), Error::::OverflowRisk ); // and a sanity check - assert_ok!(pool.ok_to_join_with(&123, Balance::MAX / 100 - bonded + 1),); + assert_ok!(pool.ok_to_join_with(Balance::MAX / 100 - bonded + 1),); }); } } @@ -328,6 +328,7 @@ mod join { #[test] fn join_works() { + let bonded = |points| BondedPool:: { points, account: PRIMARY_ACCOUNT }; ExtBuilder::default().build_and_execute(|| { // Given Balances::make_free_balance_be(&11, ExistentialDeposit::get() + 2); @@ -347,8 +348,8 @@ mod join { } ); assert_eq!( - BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), - BondedPool:: { points: 12 } + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + bonded(12) ); // Given @@ -372,8 +373,8 @@ mod join { } ); assert_eq!( - BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), - BondedPool:: { points: 12 + 24 } + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + bonded(12 + 24) ); }); } @@ -396,7 +397,7 @@ mod join { Error::::OverflowRisk ); - BondedPools::::insert(123, BondedPool:: { points: 100 }); + BondedPool::::insert(BondedPool:: { points: 100, account: 123 }); // Force the points:balance ratio to 100/10 (so 10) StakingMock::set_bonded_balance(123, 10); assert_noop!(Pools::join(Origin::signed(11), 420, 123), Error::::OverflowRisk); @@ -634,7 +635,7 @@ mod claim_payout { #[test] fn calculate_delegator_payout_errors_if_a_delegator_is_unbonding() { ExtBuilder::default().build_and_execute(|| { - let bonded_pool = BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); let reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); delegator.unbonding_era = Some(0); @@ -649,13 +650,9 @@ mod claim_payout { #[test] fn calculate_delegator_payout_works_with_a_pool_of_1() { let del = |reward_pool_total_earnings| del(10, reward_pool_total_earnings); - // pool: PRIMARY_ACCOUNT, - // points: 10, - // reward_pool_total_earnings, - // unbonding_era: None, - // }; + ExtBuilder::default().build_and_execute(|| { - let bonded_pool = BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); let reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); let delegator = Delegators::::get(10).unwrap(); @@ -712,7 +709,7 @@ mod claim_payout { ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - let bonded_pool = BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); let reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); // Delegator with 10 points let del_10 = Delegators::::get(10).unwrap(); @@ -897,11 +894,11 @@ mod claim_payout { ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - let bonded_pool = BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); // Given the bonded pool has 100 points assert_eq!(bonded_pool.points, 100); - // Each delegator currently has a free balance of - + // Each delegator currently has a free balance of Balances::make_free_balance_be(&10, 0); Balances::make_free_balance_be(&40, 0); Balances::make_free_balance_be(&50, 0); @@ -1089,7 +1086,7 @@ mod claim_payout { #[test] fn do_reward_payout_errors_correctly() { ExtBuilder::default().build_and_execute(|| { - let bonded_pool = BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); // The only place this can return an error is with the balance transfer from the @@ -1125,8 +1122,8 @@ mod unbond { ); assert_eq!( - BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), - BondedPool { points: 0 } + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool { points: 0, account: PRIMARY_ACCOUNT } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); @@ -1152,8 +1149,8 @@ mod unbond { sub_pools_with_era! { 0 => UnbondPool { points: 6, balance: 6 }} ); assert_eq!( - BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), - BondedPool { points: 560 } + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool { points: 560, account: PRIMARY_ACCOUNT } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 94); assert_eq!(Delegators::::get(40).unwrap().unbonding_era, Some(0)); @@ -1168,8 +1165,8 @@ mod unbond { sub_pools_with_era! { 0 => UnbondPool { points: 7, balance: 7 }} ); assert_eq!( - BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), - BondedPool { points: 550 } + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool { points: 550, account: PRIMARY_ACCOUNT } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 93); assert_eq!(Delegators::::get(10).unwrap().unbonding_era, Some(0)); @@ -1184,8 +1181,8 @@ mod unbond { sub_pools_with_era! { 0 => UnbondPool { points: 100, balance: 100 }} ); assert_eq!( - BondedPools::::get(&PRIMARY_ACCOUNT).unwrap(), - BondedPool { points: 0 } + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool { points: 0, account: PRIMARY_ACCOUNT } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0)); @@ -1246,8 +1243,8 @@ mod unbond { assert_noop!(Pools::unbond(Origin::signed(11), 0), Error::::PoolNotFound); // Add bonded pool to go along with the delegator - let bonded_pool = BondedPool { points: 10 }; - BondedPools::::insert(1, bonded_pool); + let bonded_pool = BondedPool { points: 10, account: 1 }; + BondedPool::::insert(bonded_pool); assert_noop!( Pools::unbond(Origin::signed(11), 0), @@ -1483,7 +1480,7 @@ mod create { ExtBuilder::default().build_and_execute(|| { let stash = 337692978; - assert!(!BondedPools::::contains_key(1)); + assert!(!BondedPoolPoints::::contains_key(1)); assert!(!RewardPools::::contains_key(1)); assert!(!Delegators::::contains_key(11)); assert_eq!(StakingMock::bonded_balance(&stash), None); @@ -1502,8 +1499,8 @@ mod create { } ); assert_eq!( - BondedPools::::get(stash).unwrap(), - BondedPool { points: StakingMock::minimum_bond() } + BondedPool::::get(&stash).unwrap(), + BondedPool { points: StakingMock::minimum_bond(), account: stash.clone() } ); assert_eq!(StakingMock::bonded_balance(&stash).unwrap(), StakingMock::minimum_bond()); assert_eq!( From 4834c94fe21676e388579c4db95df893af8f133a Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 18:23:28 -0800 Subject: [PATCH 067/299] Use term pool_account, not stash --- frame/pools/src/lib.rs | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 1ebe58345c02a..b5fd2c886ca24 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -593,7 +593,7 @@ pub mod pallet { #[pallet::storage] pub(crate) type PoolIds = CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId>; - /// Points for the bonded pools. Keyed by the pool's account. To get or insert a pool see + /// Points for the bonded pools. Keyed by the pool's _Stash_/_Controller_. To get or insert a pool see /// [`BondedPool::get`] and [`BondedPool::insert`] #[pallet::storage] pub(crate) type BondedPoolPoints = @@ -893,38 +893,38 @@ pub mod pallet { let who = ensure_signed(origin)?; ensure!(amount >= T::StakingInterface::minimum_bond(), Error::::MinimumBondNotMet); - let (stash, reward_dest) = Self::create_accounts(index); - ensure!(!BondedPoolPoints::::contains_key(&stash), Error::::IdInUse); - T::StakingInterface::bond_checks(&stash, &stash, amount, &reward_dest)?; - let (stash, targets) = T::StakingInterface::nominate_checks(&stash, targets)?; + let (pool_account, reward_account) = Self::create_accounts(index); + ensure!(!BondedPoolPoints::::contains_key(&pool_account), Error::::IdInUse); + T::StakingInterface::bond_checks(&pool_account, &pool_account, amount, &reward_account)?; + let (pool_account, targets) = T::StakingInterface::nominate_checks(&pool_account, targets)?; - let mut bonded_pool = BondedPool:: { points: Zero::zero(), account: stash.clone() }; + let mut bonded_pool = BondedPool:: { points: Zero::zero(), account: pool_account.clone() }; // We must calculate the points to issue *before* we bond who's funds, else // points:balance ratio will be wrong. let points_to_issue = bonded_pool.points_to_issue(amount); bonded_pool.points = points_to_issue; - T::Currency::transfer(&who, &stash, amount, ExistenceRequirement::AllowDeath)?; + T::Currency::transfer(&who, &pool_account, amount, ExistenceRequirement::AllowDeath)?; T::StakingInterface::bond( - stash.clone(), + pool_account.clone(), // We make the stash and controller the same for simplicity - stash.clone(), + pool_account.clone(), amount, - reward_dest.clone(), + reward_account.clone(), ) .map_err(|e| { log!(warn, "error trying to bond new pool after a users balance was transferred."); e })?; - T::StakingInterface::unchecked_nominate(&stash, targets); + T::StakingInterface::unchecked_nominate(&pool_account, targets); Delegators::::insert( who, Delegator:: { - pool: stash.clone(), + pool: pool_account.clone(), points: points_to_issue, reward_pool_total_earnings: Zero::zero(), unbonding_era: None, @@ -932,12 +932,12 @@ pub mod pallet { ); BondedPool::::insert(bonded_pool); RewardPools::::insert( - stash, + pool_account, RewardPool:: { balance: Zero::zero(), points: U256::zero(), total_earnings: Zero::zero(), - account_id: reward_dest, + account_id: reward_account, }, ); @@ -1084,6 +1084,8 @@ impl Pallet { active_bonded, }: SlashPoolArgs::>, ) -> Option>> { + // Make sure this is a pool account + BondedPoolPoints::::contains_key(&pool_stash).then(|| ())?; let mut sub_pools = SubPoolsStorage::::get(pool_stash).unwrap_or_default(); let affected_range = (slash_era + 1)..=apply_era; @@ -1101,14 +1103,12 @@ impl Pallet { // Note that the balances of the bonded pool and its affected sub-pools will saturated at // zero if slash_amount > total_affected_balance let total_affected_balance = active_bonded.saturating_add(unbonding_affected_balance); - if total_affected_balance.is_zero() { return Some(SlashPoolOut { slashed_bonded: Zero::zero(), slashed_unlocking: Default::default(), }) } - let slashed_unlocking: BTreeMap<_, _> = affected_range .filter_map(|era| { if let Some(mut unbond_pool) = sub_pools.with_era.get_mut(&era) { @@ -1131,7 +1131,6 @@ impl Pallet { } }) .collect(); - SubPoolsStorage::::insert(pool_stash, sub_pools); // Equivalent to `(slash_amount / total_affected_balance) * active_bonded` @@ -1143,7 +1142,6 @@ impl Pallet { active_bonded.saturating_sub(bonded_pool_slash_amount) }; - Some(SlashPoolOut { slashed_bonded, slashed_unlocking }) } } From 63377e106555c7d681ad434c0ed4cfcfad0d498b Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 18:30:51 -0800 Subject: [PATCH 068/299] Use term validators, not targets --- frame/pools/src/lib.rs | 61 +++++++++++++++++++++------------------- frame/pools/src/tests.rs | 22 ++++++--------- 2 files changed, 40 insertions(+), 43 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index b5fd2c886ca24..b952ad2a8f080 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -211,12 +211,12 @@ //! //! TBD - possible options: //! * Pools can be created by anyone but nominations can never be updated -//! * Pools can be created by anyone and the creator can update the targets -//! * Pools are created by governance and governance can update the targets +//! * Pools can be created by anyone and the creator can update the validators +//! * Pools are created by governance and governance can update the validators //! ... Other ideas //! * pools can have different roles assigned: creator (puts deposit down, cannot remove deposit //! until pool is empty), admin (can control who is nominator, destroyer and can do -//! nominate/destroy), nominator (can adjust targets), destroyer (can initiate destroy), etc +//! nominate/destroy), nominator (can adjust nominators), destroyer (can initiate destroy), etc //! //! # Runtime builder warnings //! @@ -233,7 +233,7 @@ // - creator: account that cannont unbond until there are no other pool members (essentially // deposit) // - kicker: can kick (force unbond) delegators and block new delegators -// - nominator: can set targets +// - nominator: can set nominations // - admin: can change kicker, nominator, and make another account admin. // - checks for number of pools when creating pools (param for max pools, pool creation origin) // - post checks that rewardpool::count == bondedpool::count. delegators >= bondedpool::count, @@ -366,25 +366,22 @@ impl BondedPool { /// Get the amount of points to issue for some new funds that will be bonded in the pool. fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { - let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); + let bonded_balance = + T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); points_to_issue::(bonded_balance, self.points, new_funds) } // Get the amount of balance to unbond from the pool based on a delegator's points of the pool. - fn balance_to_unbond( - &self, - delegator_points: BalanceOf, - ) -> BalanceOf { - let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); + fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { + let bonded_balance = + T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); balance_to_unbond::(bonded_balance, self.points, delegator_points) } // Check that the pool can accept a member with `new_funds`. - fn ok_to_join_with( - &self, - new_funds: BalanceOf, - ) -> Result<(), DispatchError> { - let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); + fn ok_to_join_with(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { + let bonded_balance = + T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); let points_to_balance_ratio_floor = self @@ -417,7 +414,7 @@ impl BondedPool { #[scale_info(skip_type_params(T))] pub struct RewardPool { /// The reward destination for the pool. - account_id: T::AccountId, + account: T::AccountId, /// The balance of this reward pool after the last claimed payout. balance: BalanceOf, /// The total earnings _ever_ of this reward pool after the last claimed payout. I.E. the sum @@ -433,7 +430,7 @@ pub struct RewardPool { impl RewardPool { fn update_total_earnings_and_balance(mut self) -> Self { - let current_balance = T::Currency::free_balance(&self.account_id); + let current_balance = T::Currency::free_balance(&self.account); // The earnings since the last time it was updated let new_earnings = current_balance.saturating_sub(self.balance); // The lifetime earnings of the of the reward pool @@ -593,8 +590,8 @@ pub mod pallet { #[pallet::storage] pub(crate) type PoolIds = CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId>; - /// Points for the bonded pools. Keyed by the pool's _Stash_/_Controller_. To get or insert a pool see - /// [`BondedPool::get`] and [`BondedPool::insert`] + /// Points for the bonded pools. Keyed by the pool's _Stash_/_Controller_. To get or insert a + /// pool see [`BondedPool::get`] and [`BondedPool::insert`] #[pallet::storage] pub(crate) type BondedPoolPoints = CountedStorageMap<_, Twox64Concat, T::AccountId, BalanceOf>; @@ -776,8 +773,7 @@ pub mod pallet { let sub_pools = SubPoolsStorage::::get(&delegator.pool).unwrap_or_default(); let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); - let balance_to_unbond = - bonded_pool.balance_to_unbond(delegator.points); + let balance_to_unbond = bonded_pool.balance_to_unbond(delegator.points); // Update the bonded pool. Note that we must do this *after* calculating the balance // to unbond so we have the correct points for the balance:share ratio. @@ -879,14 +875,14 @@ pub mod pallet { /// Create a pool. /// - /// * `targets`: _Stash_ addresses of the validators to nominate + /// * `validators`: _Stash_ addresses of the validators to nominate. /// * `amount`: Balance to delegate to the pool. Must meet the minimum bond. /// * `index`: Disambiguation index for seeding account generation. Likely only useful when /// creating multiple pools in the same extrinsic. #[pallet::weight(666)] pub fn create( origin: OriginFor, - targets: Vec<::Source>, + validators: Vec<::Source>, amount: BalanceOf, index: u16, ) -> DispatchResult { @@ -895,10 +891,17 @@ pub mod pallet { let (pool_account, reward_account) = Self::create_accounts(index); ensure!(!BondedPoolPoints::::contains_key(&pool_account), Error::::IdInUse); - T::StakingInterface::bond_checks(&pool_account, &pool_account, amount, &reward_account)?; - let (pool_account, targets) = T::StakingInterface::nominate_checks(&pool_account, targets)?; + T::StakingInterface::bond_checks( + &pool_account, + &pool_account, + amount, + &reward_account, + )?; + let (pool_account, validators) = + T::StakingInterface::nominate_checks(&pool_account, validators)?; - let mut bonded_pool = BondedPool:: { points: Zero::zero(), account: pool_account.clone() }; + let mut bonded_pool = + BondedPool:: { points: Zero::zero(), account: pool_account.clone() }; // We must calculate the points to issue *before* we bond who's funds, else // points:balance ratio will be wrong. @@ -919,7 +922,7 @@ pub mod pallet { e })?; - T::StakingInterface::unchecked_nominate(&pool_account, targets); + T::StakingInterface::unchecked_nominate(&pool_account, validators); Delegators::::insert( who, @@ -937,7 +940,7 @@ pub mod pallet { balance: Zero::zero(), points: U256::zero(), total_earnings: Zero::zero(), - account_id: reward_account, + account: reward_account, }, ); @@ -1062,7 +1065,7 @@ impl Pallet { // Transfer payout to the delegator. Self::transfer_reward( - &reward_pool.account_id, + &reward_pool.account, delegator_id.clone(), delegator.pool.clone(), delegator_payout, diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index bac01902fc3bf..0850afa1752c8 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -32,7 +32,7 @@ fn test_setup_works() { balance: 0, points: 0.into(), total_earnings: 0, - account_id: REWARDS_ACCOUNT + account: REWARDS_ACCOUNT } ); assert_eq!( @@ -347,10 +347,7 @@ mod join { unbonding_era: None } ); - assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), - bonded(12) - ); + assert_eq!(BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), bonded(12)); // Given // The bonded balance is slashed in half @@ -372,10 +369,7 @@ mod join { unbonding_era: None } ); - assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), - bonded(12 + 24) - ); + assert_eq!(BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), bonded(12 + 24)); }); } @@ -424,7 +418,7 @@ mod join { balance: Zero::zero(), points: U256::zero(), total_earnings: Zero::zero(), - account_id: 321, + account: 321, }, ); }); @@ -439,7 +433,7 @@ mod claim_payout { } fn rew(balance: Balance, points: u32, total_earnings: Balance) -> RewardPool { - RewardPool { balance, points: points.into(), total_earnings, account_id: REWARDS_ACCOUNT } + RewardPool { balance, points: points.into(), total_earnings, account: REWARDS_ACCOUNT } } #[test] @@ -667,7 +661,7 @@ mod claim_payout { assert_eq!(reward_pool, rew(0, 0, 0)); // Given the pool has earned some rewards for the first time - Balances::make_free_balance_be(&reward_pool.account_id, 5); + Balances::make_free_balance_be(&reward_pool.account, 5); // When let (reward_pool, delegator, payout) = @@ -1166,7 +1160,7 @@ mod unbond { ); assert_eq!( BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), - BondedPool { points: 550, account: PRIMARY_ACCOUNT } + BondedPool { points: 550, account: PRIMARY_ACCOUNT } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 93); assert_eq!(Delegators::::get(10).unwrap().unbonding_era, Some(0)); @@ -1509,7 +1503,7 @@ mod create { balance: Zero::zero(), points: U256::zero(), total_earnings: Zero::zero(), - account_id: 2844952004 + account: 2844952004 } ); }); From d0ebdc1458c45bddfa241e4bf89f74d60a65a635 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 19:16:25 -0800 Subject: [PATCH 069/299] Make some extrinsics transactional --- frame/pools/src/lib.rs | 59 +++++++++++++---------------------- frame/pools/src/mock.rs | 33 +------------------- frame/pools/src/tests.rs | 26 +++------------ primitives/staking/src/lib.rs | 23 +------------- 4 files changed, 28 insertions(+), 113 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index b952ad2a8f080..e1bd51529c662 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -222,12 +222,15 @@ //! //! * watch out for overflow of [`RewardPoints`] and [`BalanceOf`] types. Consider things like the //! chains total issuance, staking reward rate, and burn rate. +// Invariants +// - A `delegator.pool` must always be a valid entry in `RewardPools`, and `BondedPoolPoints`. +// - Every entry in `BondedPoolPoints` must have a corresponding entry in `RewardPools` #![cfg_attr(not(feature = "std"), no_std)] // TODO: -// - make withdraw unbonded permissions-less and remove withdraw unbonded call from unbond -// - make claiming rewards permissionless +// - make withdraw unbonded pool that just calls withdraw unbonded for the underlying pool account - +// and remove withdraw unbonded call from unbond // - creation // - CreateOrigin: the type of origin that can create a pool - can be set with governance call // - creator: account that cannont unbond until there are no other pool members (essentially @@ -271,7 +274,6 @@ macro_rules! log { }; } -type PoolId = u32; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type SubPoolsWithEra = BoundedBTreeMap, MaxUnbonding>; @@ -585,11 +587,6 @@ pub mod pallet { pub(crate) type Delegators = CountedStorageMap<_, Twox64Concat, T::AccountId, Delegator>; - /// `PoolId` lookup from the pool's `AccountId`. Useful for pool lookup from the slashing - /// system. - #[pallet::storage] - pub(crate) type PoolIds = CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId>; - /// Points for the bonded pools. Keyed by the pool's _Stash_/_Controller_. To get or insert a /// pool see [`BondedPool::get`] and [`BondedPool::insert`] #[pallet::storage] @@ -660,43 +657,36 @@ pub mod pallet { /// * this will *not* dust the delegator account, so the delegator must have at least /// `existential deposit + amount` in their account. #[pallet::weight(666)] + #[frame_support::transactional] pub fn join( origin: OriginFor, amount: BalanceOf, - target: T::AccountId, + pool_account: T::AccountId, ) -> DispatchResult { let who = ensure_signed(origin)?; // If a delegator already exists that means they already belong to a pool ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); - // let mut bonded_pool = - // BondedPools::::get(&target).ok_or(Error::::PoolNotFound)?; - let mut bonded_pool = BondedPool::::get(&target).ok_or(Error::::PoolNotFound)?; + let mut bonded_pool = + BondedPool::::get(&pool_account).ok_or(Error::::PoolNotFound)?; bonded_pool.ok_to_join_with(amount)?; - // The pool should always be created in such a way its in a state to bond extra, but if - // the active balance is slashed below the minimum bonded or the account cannot be - // found, we exit early. - if !T::StakingInterface::can_bond_extra(&target, amount) { - Err(Error::::StakingError)?; - } - // We don't actually care about writing the reward pool, we just need its // total earnings at this point in time. - let reward_pool = RewardPools::::get(&target) + let reward_pool = RewardPools::::get(&pool_account) .ok_or(Error::::RewardPoolNotFound)? // This is important because we want the most up-to-date total earnings. .update_total_earnings_and_balance(); - let old_free_balance = T::Currency::free_balance(&target); + let old_free_balance = T::Currency::free_balance(&pool_account); // Transfer the funds to be bonded from `who` to the pools account so the pool can then // go bond them. - T::Currency::transfer(&who, &target, amount, ExistenceRequirement::KeepAlive)?; + T::Currency::transfer(&who, &pool_account, amount, ExistenceRequirement::KeepAlive)?; // Try not to bail after the above transfer // This should now include the transferred balance. - let new_free_balance = T::Currency::free_balance(&target); + let new_free_balance = T::Currency::free_balance(&pool_account); // Get the exact amount we can bond extra. let exact_amount_to_bond = new_free_balance.saturating_sub(old_free_balance); // We must calculate the points to issue *before* we bond `who`'s funds, else the @@ -704,12 +694,15 @@ pub mod pallet { let new_points = bonded_pool.points_to_issue(exact_amount_to_bond); bonded_pool.points = bonded_pool.points.saturating_add(new_points); - T::StakingInterface::bond_extra(target.clone(), exact_amount_to_bond)?; + // The pool should always be created in such a way its in a state to bond extra, but if + // the active balance is slashed below the minimum bonded or the account cannot be + // found, we exit early. + T::StakingInterface::bond_extra(pool_account.clone(), exact_amount_to_bond)?; Delegators::insert( who.clone(), Delegator:: { - pool: target.clone(), + pool: pool_account.clone(), points: new_points, // At best the reward pool has the rewards up through the previous era. If the // delegator joins prior to the snapshot they will benefit from the rewards of @@ -725,7 +718,7 @@ pub mod pallet { Self::deposit_event(Event::::Joined { delegator: who, - pool: target, + pool: pool_account, bonded: exact_amount_to_bond, }); @@ -880,6 +873,7 @@ pub mod pallet { /// * `index`: Disambiguation index for seeding account generation. Likely only useful when /// creating multiple pools in the same extrinsic. #[pallet::weight(666)] + #[frame_support::transactional] pub fn create( origin: OriginFor, validators: Vec<::Source>, @@ -891,25 +885,15 @@ pub mod pallet { let (pool_account, reward_account) = Self::create_accounts(index); ensure!(!BondedPoolPoints::::contains_key(&pool_account), Error::::IdInUse); - T::StakingInterface::bond_checks( - &pool_account, - &pool_account, - amount, - &reward_account, - )?; - let (pool_account, validators) = - T::StakingInterface::nominate_checks(&pool_account, validators)?; let mut bonded_pool = BondedPool:: { points: Zero::zero(), account: pool_account.clone() }; - // We must calculate the points to issue *before* we bond who's funds, else // points:balance ratio will be wrong. let points_to_issue = bonded_pool.points_to_issue(amount); bonded_pool.points = points_to_issue; T::Currency::transfer(&who, &pool_account, amount, ExistenceRequirement::AllowDeath)?; - T::StakingInterface::bond( pool_account.clone(), // We make the stash and controller the same for simplicity @@ -921,8 +905,7 @@ pub mod pallet { log!(warn, "error trying to bond new pool after a users balance was transferred."); e })?; - - T::StakingInterface::unchecked_nominate(&pool_account, validators); + T::StakingInterface::nominate(&pool_account, validators); Delegators::::insert( who, diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index 8fa9c037b8eeb..cb0100d07bbbb 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -15,9 +15,6 @@ parameter_types! { pub static CurrentEra: EraIndex = 0; static BondedBalanceMap: std::collections::HashMap = Default::default(); static UnbondingBalanceMap: std::collections::HashMap = Default::default(); - pub static CanBondExtra: bool = true; - pub static CanBond: bool = true; - pub static CanNominate: bool = true; pub static BondingDuration: EraIndex = 3; pub static DisableWithdrawUnbonded: bool = false; } @@ -50,10 +47,6 @@ impl sp_staking::StakingInterface for StakingMock { BondedBalanceMap::get().get(who).map(|v| *v) } - fn can_bond_extra(_: &Self::AccountId, _: Self::Balance) -> bool { - CanBondExtra::get() - } - fn bond_extra(who: Self::AccountId, extra: Self::Balance) -> DispatchResult { BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(&who).unwrap() += extra); Ok(()) @@ -81,19 +74,6 @@ impl sp_staking::StakingInterface for StakingMock { Ok(100) } - fn bond_checks( - _: &Self::AccountId, - _: &Self::AccountId, - _: Self::Balance, - _: &Self::AccountId, - ) -> Result<(), DispatchError> { - if CanBond::get() { - Ok(()) - } else { - Err(Error::::StakingError.into()) - } - } - fn bond( stash: Self::AccountId, _: Self::AccountId, @@ -104,18 +84,7 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } - fn nominate_checks( - controller_and_stash: &Self::AccountId, - targets: Vec, - ) -> Result<(Self::AccountId, Vec), DispatchError> { - if CanNominate::get() { - Ok((controller_and_stash.clone(), targets)) - } else { - Err(Error::::StakingError.into()) - } - } - - fn unchecked_nominate(_: &Self::AccountId, _: Vec) {} + fn nominate(_: &Self::AccountId, _: Vec) {} } impl frame_system::Config for Runtime { diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 0850afa1752c8..152194068ec54 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1,8 +1,8 @@ use super::*; use crate::mock::{ - Balance, Balances, BondingDuration, CanBond, CanBondExtra, CanNominate, CurrentEra, - DisableWithdrawUnbonded, ExistentialDeposit, ExtBuilder, Origin, Pools, Runtime, StakingMock, - PRIMARY_ACCOUNT, REWARDS_ACCOUNT, UNBONDING_BALANCE_MAP, + Balance, Balances, BondingDuration, CurrentEra, DisableWithdrawUnbonded, ExistentialDeposit, + ExtBuilder, Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, + UNBONDING_BALANCE_MAP, }; use frame_support::{assert_noop, assert_ok}; @@ -373,6 +373,7 @@ mod join { }); } + // TODO: test transactional storage aspect #[test] fn join_errors_correctly() { use super::*; @@ -404,10 +405,6 @@ mod join { Error::::OverflowRisk ); - CanBondExtra::set(false); - assert_noop!(Pools::join(Origin::signed(11), 420, 123), Error::::StakingError); - CanBondExtra::set(true); - assert_noop!( Pools::join(Origin::signed(11), 420, 123), Error::::RewardPoolNotFound @@ -1509,6 +1506,7 @@ mod create { }); } + // TODO check transactional storage aspect #[test] fn create_errors_correctly() { ExtBuilder::default().build_and_execute(|| { @@ -1521,20 +1519,6 @@ mod create { Pools::create(Origin::signed(11), vec![], 1, 42), Error::::MinimumBondNotMet ); - - CanNominate::set(false); - assert_noop!( - Pools::create(Origin::signed(11), vec![], 420, 42), - Error::::StakingError - ); - CanNominate::set(true); - - CanBond::set(false); - assert_noop!( - Pools::create(Origin::signed(11), vec![], 420, 42), - Error::::StakingError - ); - CanBond::set(true); }); } } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 6d19b4584569b..a0f53e59626ad 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -105,10 +105,6 @@ pub trait StakingInterface { /// Balance `controller` has bonded for nominating. fn bonded_balance(controller: &Self::AccountId) -> Option; - /// If the given staker can successfully call `bond_extra` with `extra`. Assumes the `extra` - /// balance will be transferred in the stash. - fn can_bond_extra(controller: &Self::AccountId, extra: Self::Balance) -> bool; - fn bond_extra(controller: Self::AccountId, extra: Self::Balance) -> DispatchResult; fn unbond(controller: Self::AccountId, value: Self::Balance) -> DispatchResult; @@ -118,16 +114,6 @@ pub trait StakingInterface { num_slashing_spans: u32, ) -> Result; - /// Check if the given accounts can be bonded as stash <-> controller pair and with the given - /// reward destination. Does not check if the accounts have enough funds. It is assumed that the - /// necessary funds will only be transferred into the accounts after this check is completed. - fn bond_checks( - stash: &Self::AccountId, - controller: &Self::AccountId, - value: Self::Balance, - payee: &Self::AccountId, - ) -> Result<(), DispatchError>; - fn bond( stash: Self::AccountId, controller: Self::AccountId, @@ -135,12 +121,5 @@ pub trait StakingInterface { payee: Self::AccountId, ) -> DispatchResult; - /// Check if the given account can nominate. Assumes the account will be correctly bonded after - /// this call. Returns stash and targets if the checks pass. - fn nominate_checks( - controller: &Self::AccountId, - targets: Vec, - ) -> Result<(Self::AccountId, Vec), DispatchError>; - - fn unchecked_nominate(stash: &Self::AccountId, targets: Vec); + fn nominate(stash: &Self::AccountId, targets: Vec); } From 72ec6511a01eecf787c2ccb8e3c2bb504f3d2c47 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 19:30:22 -0800 Subject: [PATCH 070/299] Use defensive map_err --- frame/pools/src/lib.rs | 14 ++++---------- frame/pools/src/tests.rs | 33 ++++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index e1bd51529c662..64f7160b2d344 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -246,7 +246,7 @@ use frame_support::{ ensure, pallet_prelude::*, storage::bounded_btree_map::BoundedBTreeMap, - traits::{Currency, ExistenceRequirement, Get}, + traits::{Currency, DefensiveResult, ExistenceRequirement, Get}, DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; @@ -818,6 +818,7 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); + // TODO: make this an ensure if current_era.saturating_sub(unbonding_era) < T::StakingInterface::bonding_duration() { return Err(Error::::NotUnbondedYet.into()) }; @@ -849,10 +850,7 @@ pub mod pallet { balance_to_unbond, ExistenceRequirement::AllowDeath, ) - .map_err(|e| { - log::warn!("system logic error: pool could not withdraw_unbonded due to lack of free balance"); - e - })?; + .defensive_map_err(|e| e)?; SubPoolsStorage::::insert(&delegator.pool, sub_pools); Delegators::::remove(&who); @@ -900,11 +898,7 @@ pub mod pallet { pool_account.clone(), amount, reward_account.clone(), - ) - .map_err(|e| { - log!(warn, "error trying to bond new pool after a users balance was transferred."); - e - })?; + )?; T::StakingInterface::nominate(&pool_account, validators); Delegators::::insert( diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 152194068ec54..b9b75ebf5670e 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1450,17 +1450,40 @@ mod withdraw_unbonded { }; SubPoolsStorage::::insert(123, sub_pools.clone()); - assert_noop!( - Pools::withdraw_unbonded(Origin::signed(11), 0), - pallet_balances::Error::::InsufficientBalance - ); - // If we error the delegator does not get removed assert_eq!(Delegators::::get(&11), Some(delegator)); // and the subpools do not get updated. assert_eq!(SubPoolsStorage::::get(123).unwrap(), sub_pools) }); } + + #[test] + #[should_panic = "Defensive failure has been triggered!: Module { index: 1, error: 2, message: Some(\"InsufficientBalance\") }"] + fn withdraw_unbonded_test_panics_if_funds_cannot_be_transferred() { + ExtBuilder::default().build_and_execute(|| { + // Insert a delegator that starts unbonding in era 0 + let mut delegator = Delegator { + pool: 123, + points: 10, + reward_pool_total_earnings: 0, + unbonding_era: Some(0), + }; + Delegators::::insert(11, delegator.clone()); + + // Skip ahead to the end of the bonding duration + CurrentEra::set(StakingMock::bonding_duration()); + + // Insert the sub-pool + let sub_pools = SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { 0 => UnbondPool { points: 10, balance: 10 }}, + }; + SubPoolsStorage::::insert(123, sub_pools.clone()); + + // Panics + Pools::withdraw_unbonded(Origin::signed(11), 0); + }); + } } mod create { From 8c0feda9cd14999209d3bc5e19384b95c9e7a96e Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 19:55:22 -0800 Subject: [PATCH 071/299] Use issue, not points to issue --- frame/pools/src/lib.rs | 44 +++++++++++++++++++++++----------------- frame/pools/src/tests.rs | 4 ++-- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 64f7160b2d344..3b617547498f8 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -225,6 +225,8 @@ // Invariants // - A `delegator.pool` must always be a valid entry in `RewardPools`, and `BondedPoolPoints`. // - Every entry in `BondedPoolPoints` must have a corresponding entry in `RewardPools` +// - If a delegator unbonds, the sub pools should always correctly track slashses such that the +// calculated amount when withdrawing unbonded is a lower bound of the pools free balance. #![cfg_attr(not(feature = "std"), no_std)] @@ -355,14 +357,14 @@ pub struct BondedPool { impl BondedPool { /// Get [`Self`] from storage. Returns `None` if no entry for `pool_account` exists. - pub fn get(pool_account: &T::AccountId) -> Option { + fn get(pool_account: &T::AccountId) -> Option { BondedPoolPoints::::try_get(pool_account) .ok() .map(|points| Self { points, account: pool_account.clone() }) } /// Insert [`Self`] into storage. - pub fn insert(Self { account, points }: Self) { + fn insert(Self { account, points }: Self) { BondedPoolPoints::::insert(account, points); } @@ -373,14 +375,22 @@ impl BondedPool { points_to_issue::(bonded_balance, self.points, new_funds) } - // Get the amount of balance to unbond from the pool based on a delegator's points of the pool. + /// Get the amount of balance to unbond from the pool based on a delegator's points of the pool. fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); balance_to_unbond::(bonded_balance, self.points, delegator_points) } - // Check that the pool can accept a member with `new_funds`. + /// Issue points to [`Self`] for `new_funds`. + fn issue(&mut self, new_funds: BalanceOf) -> BalanceOf { + let points_to_issue = self.points_to_issue(new_funds); + self.points = self.points.saturating_add(points_to_issue); + + points_to_issue + } + + /// Check that the pool can accept a member with `new_funds`. fn ok_to_join_with(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); @@ -467,6 +477,12 @@ impl UnbondPool { fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { balance_to_unbond::(self.balance, self.points, delegator_points) } + + /// Issue points and update the balance given `new_balance`. + fn issue(&mut self, new_funds: BalanceOf) { + self.points = self.points.saturating_add(self.points_to_issue(new_funds)); + self.balance = self.balance.saturating_add(new_funds); + } } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound, RuntimeDebugNoBound)] @@ -683,16 +699,13 @@ pub mod pallet { // go bond them. T::Currency::transfer(&who, &pool_account, amount, ExistenceRequirement::KeepAlive)?; - // Try not to bail after the above transfer - // This should now include the transferred balance. let new_free_balance = T::Currency::free_balance(&pool_account); // Get the exact amount we can bond extra. let exact_amount_to_bond = new_free_balance.saturating_sub(old_free_balance); // We must calculate the points to issue *before* we bond `who`'s funds, else the // points:balance ratio will be wrong. - let new_points = bonded_pool.points_to_issue(exact_amount_to_bond); - bonded_pool.points = bonded_pool.points.saturating_add(new_points); + let new_points = bonded_pool.issue(exact_amount_to_bond); // The pool should always be created in such a way its in a state to bond extra, but if // the active balance is slashed below the minimum bonded or the account cannot be @@ -788,12 +801,7 @@ pub mod pallet { // Update the unbond pool associated with the current era with the // unbonded funds. Note that we lazily create the unbond pool if it // does not yet exist. - { - let mut unbond_pool = sub_pools.unchecked_with_era_get_or_make(current_era); - let points_to_issue = unbond_pool.points_to_issue(balance_to_unbond); - unbond_pool.points = unbond_pool.points.saturating_add(points_to_issue); - unbond_pool.balance = unbond_pool.balance.saturating_add(balance_to_unbond); - } + sub_pools.unchecked_with_era_get_or_make(current_era).issue(balance_to_unbond); delegator.unbonding_era = Some(current_era); @@ -886,11 +894,9 @@ pub mod pallet { let mut bonded_pool = BondedPool:: { points: Zero::zero(), account: pool_account.clone() }; - // We must calculate the points to issue *before* we bond who's funds, else + // We must calculate the points issued *before* we bond who's funds, else // points:balance ratio will be wrong. - let points_to_issue = bonded_pool.points_to_issue(amount); - bonded_pool.points = points_to_issue; - + let points_issued = bonded_pool.issue(amount); T::Currency::transfer(&who, &pool_account, amount, ExistenceRequirement::AllowDeath)?; T::StakingInterface::bond( pool_account.clone(), @@ -905,7 +911,7 @@ pub mod pallet { who, Delegator:: { pool: pool_account.clone(), - points: points_to_issue, + points: points_issued, reward_pool_total_earnings: Zero::zero(), unbonding_era: None, }, diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index b9b75ebf5670e..88af5516ef365 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -1462,7 +1462,7 @@ mod withdraw_unbonded { fn withdraw_unbonded_test_panics_if_funds_cannot_be_transferred() { ExtBuilder::default().build_and_execute(|| { // Insert a delegator that starts unbonding in era 0 - let mut delegator = Delegator { + let delegator = Delegator { pool: 123, points: 10, reward_pool_total_earnings: 0, @@ -1481,7 +1481,7 @@ mod withdraw_unbonded { SubPoolsStorage::::insert(123, sub_pools.clone()); // Panics - Pools::withdraw_unbonded(Origin::signed(11), 0); + let _ = Pools::withdraw_unbonded(Origin::signed(11), 0); }); } } From 998bd00e236dd86aeb95b61b7e1296c7f4b1e072 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 20:01:53 -0800 Subject: [PATCH 072/299] Add some notes about create & TODOs for destorying --- frame/pools/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 3b617547498f8..e670cb35cddd3 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -762,6 +762,9 @@ pub mod pallet { #[pallet::weight(666)] pub fn unbond(origin: OriginFor, num_slashing_spans: u32) -> DispatchResult { let who = ensure_signed(origin)?; + // TODO check if this is owner, if its the owner then they must be the only person in + // the pool and it needs to be destroyed with withdraw_unbonded + let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; // let mut bonded_pool = // BondedPools::::get(&delegator.pool).ok_or(Error::::PoolNotFound)?; @@ -822,6 +825,10 @@ pub mod pallet { #[pallet::weight(666)] pub fn withdraw_unbonded(origin: OriginFor, num_slashing_spans: u32) -> DispatchResult { let who = ensure_signed(origin)?; + // TODO: Check if this is the owner, and if its the owner then we delete this pool from + // storage at the end of the function. (And we assume that unbond ensured they are the + // last member of the pool) + let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; @@ -874,6 +881,9 @@ pub mod pallet { /// Create a pool. /// + /// Note that the pool creator will delegate `amount` to the pool and cannot unbond until + /// every + /// /// * `validators`: _Stash_ addresses of the validators to nominate. /// * `amount`: Balance to delegate to the pool. Must meet the minimum bond. /// * `index`: Disambiguation index for seeding account generation. Likely only useful when From fd7da1bf4b0639c29d6fabb28c37c7b53fb695eb Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 20:12:31 -0800 Subject: [PATCH 073/299] Use TotalUnbondingPools, not MaxUnbonding --- frame/pools/src/lib.rs | 33 +++++++++++++-------------------- frame/pools/src/tests.rs | 40 +++++++++++++++++++--------------------- 2 files changed, 32 insertions(+), 41 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index e670cb35cddd3..286d8562f0f28 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -155,7 +155,7 @@ //! For scalability, we maintain a bound on the number of unbonding sub pools (see [`MaxUnboning`]). //! Once we reach this bound the oldest unbonding pool is merged into the new unbonded pool (see //! `no_era` field in [`SubPools`]). In other words, a unbonding pool is removed once its older than -//! `current_era - MaxUnbonding`. We merge an unbonding pool into the unbonded pool with +//! `current_era - TotalUnbondingPools`. We merge an unbonding pool into the unbonded pool with //! //! ``` //! unbounded_pool.balance = unbounded_pool.balance + unbonding_pool.balance; @@ -278,7 +278,7 @@ macro_rules! log { type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -type SubPoolsWithEra = BoundedBTreeMap, MaxUnbonding>; +type SubPoolsWithEra = BoundedBTreeMap, TotalUnbondingPools>; // NOTE: this assumes the balance type u128 or smaller. type RewardPoints = U256; @@ -462,13 +462,6 @@ struct UnbondPool { balance: BalanceOf, } -impl UnbondPool { - #[cfg(test)] - fn new(points: BalanceOf, balance: BalanceOf) -> Self { - Self { points, balance } - } -} - impl UnbondPool { fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { points_to_issue::(self.balance, self.points, new_funds) @@ -492,7 +485,7 @@ impl UnbondPool { struct SubPools { /// A general, era agnostic pool of funds that have fully unbonded. The pools /// of `self.with_era` will lazily be merged into into this pool if they are - /// older then `current_era - MaxUnbonding`. + /// older then `current_era - TotalUnbondingPools`. no_era: UnbondPool, /// Map of era => unbond pools. with_era: SubPoolsWithEra, @@ -502,15 +495,15 @@ impl SubPools { /// Merge the oldest unbonding pool with an era into the general unbond pool with no associated /// era. fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self { - if current_era < MaxUnbonding::::get().into() { - // For the first `0..MaxUnbonding` eras of the chain we don't need to do anything. - // I.E. if `MaxUnbonding` is 5 and we are in era 4 we can add a pool for this era and - // have exactly `MaxUnbonding` pools. + if current_era < TotalUnbondingPools::::get().into() { + // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do anything. + // I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool for this era and + // have exactly `TotalUnbondingPools` pools. return self } - // I.E. if `MaxUnbonding` is 5 and current era is 10, we only want to retain pools 6..=10. - let newest_era_to_remove = current_era.saturating_sub(MaxUnbonding::::get()); + // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools 6..=10. + let newest_era_to_remove = current_era.saturating_sub(TotalUnbondingPools::::get()); let eras_to_remove: Vec<_> = self .with_era @@ -547,11 +540,11 @@ impl SubPools { /// The maximum amount of eras an unbonding pool can exist prior to being merged with the /// `no_era pool. This is guaranteed to at least be equal to the staking `UnbondingDuration`. For /// improved UX [`Config::WithEraWithdrawWindow`] should be configured to a non-zero value. -struct MaxUnbonding(PhantomData); -impl Get for MaxUnbonding { +struct TotalUnbondingPools(PhantomData); +impl Get for TotalUnbondingPools { fn get() -> u32 { // TODO: This may be too dangerous in the scenario bonding_duration gets decreased because - // we would no longer be able to decode `SubPoolsWithEra`, which uses `MaxUnbonding` as the + // we would no longer be able to decode `SubPoolsWithEra`, which uses `TotalUnbondingPools` as the // bound T::StakingInterface::bonding_duration() + T::WithEraWithdrawWindow::get() } @@ -945,7 +938,7 @@ pub mod pallet { impl Hooks> for Pallet { fn integrity_test() { assert!( - T::StakingInterface::bonding_duration() < MaxUnbonding::::get(), + T::StakingInterface::bonding_duration() < TotalUnbondingPools::::get(), "There must be more unbonding pools then the bonding duration / so a slash can be applied to relevant unboding pools. (We assume / the bonding duration > slash deffer duration.", diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 88af5516ef365..5fb0ff80e8e19 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -261,35 +261,33 @@ mod sub_pools { #[test] fn maybe_merge_pools_works() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(MaxUnbonding::::get(), 5); + assert_eq!(TotalUnbondingPools::::get(), 5); // Given let mut sub_pool_0 = SubPools:: { no_era: UnbondPool::::default(), - with_era: std::collections::BTreeMap::from([ - (0, UnbondPool::::new(10, 10)), - (1, UnbondPool::::new(10, 10)), - (2, UnbondPool::::new(20, 20)), - (3, UnbondPool::::new(30, 30)), - (4, UnbondPool::::new(40, 40)), - ]) - .try_into() - .unwrap(), + with_era: sub_pools_with_era! { + 0 => UnbondPool:: { points: 10, balance: 10 }, + 1 => UnbondPool:: { points: 10, balance: 10 }, + 2 => UnbondPool:: { points: 20, balance: 20 }, + 3 => UnbondPool:: { points: 30, balance: 30 }, + 4 => UnbondPool:: { points: 40, balance: 40 }, + } }; - // When `current_era < MaxUnbonding`, + // When `current_era < TotalUnbondingPools`, let sub_pool_1 = sub_pool_0.clone().maybe_merge_pools(3); // Then it exits early without modifications assert_eq!(sub_pool_1, sub_pool_0); - // When `current_era == MaxUnbonding`, + // When `current_era == TotalUnbondingPools`, let sub_pool_1 = sub_pool_1.maybe_merge_pools(4); // Then it exits early without modifications assert_eq!(sub_pool_1, sub_pool_0); - // When `current_era - MaxUnbonding == 0`, + // When `current_era - TotalUnbondingPools == 0`, let mut sub_pool_1 = sub_pool_1.maybe_merge_pools(5); // Then era 0 is merged into the `no_era` pool @@ -297,10 +295,10 @@ mod sub_pools { assert_eq!(sub_pool_1, sub_pool_0); // Given we have entries for era 1..=5 - sub_pool_1.with_era.try_insert(5, UnbondPool::::new(50, 50)).unwrap(); - sub_pool_0.with_era.try_insert(5, UnbondPool::::new(50, 50)).unwrap(); + sub_pool_1.with_era.try_insert(5, UnbondPool:: { points: 50, balance: 50 }).unwrap(); + sub_pool_0.with_era.try_insert(5, UnbondPool:: { points: 50, balance: 50 }).unwrap(); - // When `current_era - MaxUnbonding == 1` + // When `current_era - TotalUnbondingPools == 1` let sub_pool_2 = sub_pool_1.maybe_merge_pools(6); let era_1_pool = sub_pool_0.with_era.remove(&1).unwrap(); @@ -309,7 +307,7 @@ mod sub_pools { sub_pool_0.no_era.balance += era_1_pool.balance; assert_eq!(sub_pool_2, sub_pool_0); - // When `current_era - MaxUnbonding == 5`, so all pools with era <= 4 are removed + // When `current_era - TotalUnbondingPools == 5`, so all pools with era <= 4 are removed let sub_pool_3 = sub_pool_2.maybe_merge_pools(10); // Then all eras <= 5 are merged into the `no_era` pool @@ -1198,7 +1196,7 @@ mod unbond { ); // When - let current_era = 1 + MaxUnbonding::::get(); + let current_era = 1 + TotalUnbondingPools::::get(); CurrentEra::set(current_era); assert_ok!(Pools::unbond(Origin::signed(10), 0)); @@ -1282,7 +1280,7 @@ mod withdraw_unbonded { // Advance the current_era to ensure all `with_era` pools will be merged into // `no_era` pool - current_era += MaxUnbonding::::get(); + current_era += TotalUnbondingPools::::get(); CurrentEra::set(current_era); // Simulate some other call to unbond that would merge `with_era` pools into @@ -1617,7 +1615,7 @@ mod pools_interface { .build_and_execute(|| { // Make sure no pools get merged into `SubPools::no_era` until era 7. BondingDuration::set(5); - assert_eq!(MaxUnbonding::::get(), 7); + assert_eq!(TotalUnbondingPools::::get(), 7); assert_ok!(Pools::unbond(Origin::signed(10), 0)); @@ -1688,7 +1686,7 @@ mod pools_interface { .build_and_execute(|| { // Make sure no pools get merged into `SubPools::no_era` until era 7. BondingDuration::set(5); - assert_eq!(MaxUnbonding::::get(), 7); + assert_eq!(TotalUnbondingPools::::get(), 7); assert_ok!(Pools::unbond(Origin::signed(10), 0)); From f8430c299c310bf71b048c5fdad55e6301feb335 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 20:14:50 -0800 Subject: [PATCH 074/299] Use PostUnbondingPoolsWindow --- frame/pools/src/lib.rs | 19 ++++++++++--------- frame/pools/src/mock.rs | 4 ++-- frame/pools/src/tests.rs | 12 +++++++++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index 286d8562f0f28..f81e324754ff6 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -496,13 +496,14 @@ impl SubPools { /// era. fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self { if current_era < TotalUnbondingPools::::get().into() { - // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do anything. - // I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool for this era and - // have exactly `TotalUnbondingPools` pools. + // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do + // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool + // for this era and have exactly `TotalUnbondingPools` pools. return self } - // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools 6..=10. + // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools + // 6..=10. let newest_era_to_remove = current_era.saturating_sub(TotalUnbondingPools::::get()); let eras_to_remove: Vec<_> = self @@ -539,14 +540,14 @@ impl SubPools { /// The maximum amount of eras an unbonding pool can exist prior to being merged with the /// `no_era pool. This is guaranteed to at least be equal to the staking `UnbondingDuration`. For -/// improved UX [`Config::WithEraWithdrawWindow`] should be configured to a non-zero value. +/// improved UX [`Config::PostUnbondingPoolsWindow`] should be configured to a non-zero value. struct TotalUnbondingPools(PhantomData); impl Get for TotalUnbondingPools { fn get() -> u32 { // TODO: This may be too dangerous in the scenario bonding_duration gets decreased because - // we would no longer be able to decode `SubPoolsWithEra`, which uses `TotalUnbondingPools` as the - // bound - T::StakingInterface::bonding_duration() + T::WithEraWithdrawWindow::get() + // we would no longer be able to decode `SubPoolsWithEra`, which uses `TotalUnbondingPools` + // as the bound + T::StakingInterface::bonding_duration() + T::PostUnbondingPoolsWindow::get() } } @@ -588,7 +589,7 @@ pub mod pallet { /// able to withdraw from an unbonding pool which is guaranteed to have the correct ratio of /// points to balance; once the `with_era` pool is merged into the `no_era` pool, the ratio /// can become skewed due to some slashed ratio getting merged in at some point. - type WithEraWithdrawWindow: Get; + type PostUnbondingPoolsWindow: Get; } /// Active delegators. diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index cb0100d07bbbb..e262ceafc95e5 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -145,7 +145,7 @@ impl Convert for U256ToBalance { } parameter_types! { - pub static WithEraWithdrawWindow: u32 = 2; + pub static PostUnbondingPoolsWindow: u32 = 2; } impl pools::Config for Runtime { @@ -154,7 +154,7 @@ impl pools::Config for Runtime { type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; type StakingInterface = StakingMock; - type WithEraWithdrawWindow = WithEraWithdrawWindow; + type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 5fb0ff80e8e19..ea418e7f63ef9 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -272,7 +272,7 @@ mod sub_pools { 2 => UnbondPool:: { points: 20, balance: 20 }, 3 => UnbondPool:: { points: 30, balance: 30 }, 4 => UnbondPool:: { points: 40, balance: 40 }, - } + }, }; // When `current_era < TotalUnbondingPools`, @@ -295,8 +295,14 @@ mod sub_pools { assert_eq!(sub_pool_1, sub_pool_0); // Given we have entries for era 1..=5 - sub_pool_1.with_era.try_insert(5, UnbondPool:: { points: 50, balance: 50 }).unwrap(); - sub_pool_0.with_era.try_insert(5, UnbondPool:: { points: 50, balance: 50 }).unwrap(); + sub_pool_1 + .with_era + .try_insert(5, UnbondPool:: { points: 50, balance: 50 }) + .unwrap(); + sub_pool_0 + .with_era + .try_insert(5, UnbondPool:: { points: 50, balance: 50 }) + .unwrap(); // When `current_era - TotalUnbondingPools == 1` let sub_pool_2 = sub_pool_1.maybe_merge_pools(6); From 202153f05ba53e5b4db79091252f55885489a2b8 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Feb 2022 20:51:22 -0800 Subject: [PATCH 075/299] Use defensive for invariant gets --- frame/pools/src/lib.rs | 22 +++++++------------- frame/pools/src/tests.rs | 45 ++++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index f81e324754ff6..edc6d6bc8d3f7 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -248,7 +248,7 @@ use frame_support::{ ensure, pallet_prelude::*, storage::bounded_btree_map::BoundedBTreeMap, - traits::{Currency, DefensiveResult, ExistenceRequirement, Get}, + traits::{Currency, DefensiveResult, ExistenceRequirement, Get, DefensiveOption}, DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; @@ -684,7 +684,7 @@ pub mod pallet { // We don't actually care about writing the reward pool, we just need its // total earnings at this point in time. let reward_pool = RewardPools::::get(&pool_account) - .ok_or(Error::::RewardPoolNotFound)? + .defensive_ok_or_else(|| Error::::RewardPoolNotFound)? // This is important because we want the most up-to-date total earnings. .update_total_earnings_and_balance(); @@ -741,10 +741,8 @@ pub mod pallet { pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; - let bonded_pool = BondedPool::::get(&delegator.pool).ok_or_else(|| { - log!(error, "A bonded pool could not be found, this is a system logic error."); - Error::::PoolNotFound - })?; + let bonded_pool = BondedPool::::get(&delegator.pool) + .defensive_ok_or_else(|| Error::::PoolNotFound)?; Self::do_reward_payout(who, delegator, &bonded_pool)?; @@ -760,10 +758,8 @@ pub mod pallet { // the pool and it needs to be destroyed with withdraw_unbonded let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; - // let mut bonded_pool = - // BondedPools::::get(&delegator.pool).ok_or(Error::::PoolNotFound)?; - let mut bonded_pool = - BondedPool::::get(&delegator.pool).ok_or(Error::::PoolNotFound)?; + let mut bonded_pool = BondedPool::::get(&delegator.pool) + .defensive_ok_or_else(|| Error::::PoolNotFound)?; // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the bonded pool and thus they can no longer claim their payouts. @@ -1042,10 +1038,8 @@ impl Pallet { delegator: Delegator, bonded_pool: &BondedPool, ) -> DispatchResult { - let reward_pool = RewardPools::::get(&delegator.pool).ok_or_else(|| { - log!(error, "A reward pool could not be found, this is a system logic error."); - Error::::RewardPoolNotFound - })?; + let reward_pool = RewardPools::::get(&delegator.pool) + .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; let (reward_pool, delegator, delegator_payout) = Self::calculate_delegator_payout(bonded_pool, reward_pool, delegator)?; diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index ea418e7f63ef9..5f8fc1afb299d 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -380,7 +380,6 @@ mod join { // TODO: test transactional storage aspect #[test] fn join_errors_correctly() { - use super::*; ExtBuilder::default().build_and_execute(|| { assert_noop!( Pools::join(Origin::signed(10), 420, 420), @@ -408,20 +407,16 @@ mod join { Pools::join(Origin::signed(11), Balance::MAX / 10 - 100, 123), Error::::OverflowRisk ); + }); + } - assert_noop!( - Pools::join(Origin::signed(11), 420, 123), - Error::::RewardPoolNotFound - ); - RewardPools::::insert( - 1, - RewardPool:: { - balance: Zero::zero(), - points: U256::zero(), - total_earnings: Zero::zero(), - account: 321, - }, - ); + #[test] + #[should_panic = "Defensive failure has been triggered!"] + fn join_panics_when_reward_pool_not_found() { + ExtBuilder::default().build_and_execute(|| { + StakingMock::set_bonded_balance(123, 100); + BondedPool::::insert(BondedPool:: { points: 100, account: 123 }); + Pools::join(Origin::signed(11), 420, 123); }); } } @@ -1222,6 +1217,7 @@ mod unbond { } #[test] + #[should_panic = "Defensive failure has been triggered!"] fn unbond_errors_correctly() { ExtBuilder::default().build_and_execute(|| { assert_noop!(Pools::unbond(Origin::signed(11), 0), Error::::DelegatorNotFound); @@ -1235,16 +1231,25 @@ mod unbond { }; Delegators::::insert(11, delegator); - assert_noop!(Pools::unbond(Origin::signed(11), 0), Error::::PoolNotFound); + let _ = Pools::unbond(Origin::signed(11), 0); + }); + } - // Add bonded pool to go along with the delegator + #[test] + #[should_panic = "Defensive failure has been triggered!"] + fn unbond_panics_when_reward_pool_not_found() { + ExtBuilder::default().build_and_execute(|| { + let delegator = Delegator { + pool: 1, + points: 10, + reward_pool_total_earnings: 0, + unbonding_era: None, + }; + Delegators::::insert(11, delegator); let bonded_pool = BondedPool { points: 10, account: 1 }; BondedPool::::insert(bonded_pool); - assert_noop!( - Pools::unbond(Origin::signed(11), 0), - Error::::RewardPoolNotFound - ); + let _ = Pools::unbond(Origin::signed(11), 0); }); } } From 96f806161e00a7f2046f075ee4cfca3af5d7921e Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sat, 5 Feb 2022 16:07:24 -0800 Subject: [PATCH 076/299] Make staking compile with updated pool interface --- Cargo.lock | 2 +- frame/pools/Cargo.toml | 8 +- frame/pools/src/lib.rs | 2 +- frame/pools/src/mock.rs | 4 +- frame/pools/src/tests.rs | 4 +- frame/staking/src/pallet/impls.rs | 122 +-------------------- frame/staking/src/pallet/mod.rs | 64 ++++++++++- frame/staking/src/tests.rs | 175 ------------------------------ primitives/staking/src/lib.rs | 2 +- 9 files changed, 77 insertions(+), 306 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 811d3c4e14d75..711f323512e80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6061,7 +6061,7 @@ dependencies = [ [[package]] name = "pallet-pools" -version = "4.0.0-dev" +version = "0.0.1" dependencies = [ "frame-support", "frame-system", diff --git a/frame/pools/Cargo.toml b/frame/pools/Cargo.toml index 7131312843bac..c46b1be51e757 100644 --- a/frame/pools/Cargo.toml +++ b/frame/pools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-pools" -version = "4.0.0-dev" +version = "0.0.1" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -20,17 +20,17 @@ scale-info = { version = "1.0", default-features = false, features = ["derive"] frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-arithmetic = { version = "4.0.0", default-features = false, path = "../../primitives/arithmetic" } -sp-runtime = { version = "4.1.0-dev", default-features = false, path = "../../primitives/runtime" } +sp-runtime = { version = "5.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -sp-core = { version = "4.1.0-dev", default-features = false, path = "../../primitives/core" } +sp-core = { version = "5.0.0", default-features = false, path = "../../primitives/core" } # third party log = { version = "0.4.14", default-features = false } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io"} +sp-io = { version = "5.0.0", path = "../../primitives/io"} sp-tracing = { version = "4.0.0", path = "../../primitives/tracing" } [features] diff --git a/frame/pools/src/lib.rs b/frame/pools/src/lib.rs index edc6d6bc8d3f7..2c99a68517490 100644 --- a/frame/pools/src/lib.rs +++ b/frame/pools/src/lib.rs @@ -905,7 +905,7 @@ pub mod pallet { amount, reward_account.clone(), )?; - T::StakingInterface::nominate(&pool_account, validators); + T::StakingInterface::nominate(pool_account.clone(), validators); Delegators::::insert( who, diff --git a/frame/pools/src/mock.rs b/frame/pools/src/mock.rs index e262ceafc95e5..bbd78d6ad7c13 100644 --- a/frame/pools/src/mock.rs +++ b/frame/pools/src/mock.rs @@ -84,7 +84,9 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } - fn nominate(_: &Self::AccountId, _: Vec) {} + fn nominate(_: Self::AccountId, _: Vec) -> DispatchResult { + Ok(()) + } } impl frame_system::Config for Runtime { diff --git a/frame/pools/src/tests.rs b/frame/pools/src/tests.rs index 5f8fc1afb299d..08d5224dbeccc 100644 --- a/frame/pools/src/tests.rs +++ b/frame/pools/src/tests.rs @@ -416,7 +416,7 @@ mod join { ExtBuilder::default().build_and_execute(|| { StakingMock::set_bonded_balance(123, 100); BondedPool::::insert(BondedPool:: { points: 100, account: 123 }); - Pools::join(Origin::signed(11), 420, 123); + let _ = Pools::join(Origin::signed(11), 420, 123); }); } } @@ -1467,7 +1467,7 @@ mod withdraw_unbonded { } #[test] - #[should_panic = "Defensive failure has been triggered!: Module { index: 1, error: 2, message: Some(\"InsufficientBalance\") }"] + #[should_panic = "Defensive failure has been triggered!: Module(ModuleError { index: 1, error: 2, message: Some(\"InsufficientBalance\") })"] fn withdraw_unbonded_test_panics_if_funds_cannot_be_transferred() { ExtBuilder::default().build_and_execute(|| { // Insert a delegator that starts unbonding in era 0 diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index b2cd48b132a73..761a775b6c1e2 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -848,85 +848,6 @@ impl Pallet { DispatchClass::Mandatory, ); } - - // Checks for [`Self::bond`] that can be completed at the beginning of the calls logic. - pub(crate) fn do_bond_checks( - stash: &T::AccountId, - controller: &T::AccountId, - value: BalanceOf, - ) -> Result<(), DispatchError> { - if Bonded::::contains_key(&stash) { - Err(Error::::AlreadyBonded)? - } - - if Ledger::::contains_key(&controller) { - Err(Error::::AlreadyPaired)? - } - - // Reject a bond which is considered to be _dust_. - if value < T::Currency::minimum_balance() { - Err(Error::::InsufficientBond)? - } - - Ok(()) - } - - pub(crate) fn do_nominate_checks( - controller: &T::AccountId, - targets: Vec<::Source>, - ) -> Result<(T::AccountId, BoundedVec), DispatchError> { - let ledger = Self::ledger(controller).ok_or(Error::::NotController)?; - ensure!(ledger.active >= MinNominatorBond::::get(), Error::::InsufficientBond); - - // Only check limits if they are not already a nominator. - if !Nominators::::contains_key(&ledger.stash) { - // If this error is reached, we need to adjust the `MinNominatorBond` and start - // calling `chill_other`. Until then, we explicitly block new nominators to protect - // the runtime. - if let Some(max_nominators) = MaxNominatorsCount::::get() { - ensure!(Nominators::::count() < max_nominators, Error::::TooManyNominators); - } - } - - ensure!(!targets.is_empty(), Error::::EmptyTargets); - ensure!(targets.len() <= T::MaxNominations::get() as usize, Error::::TooManyTargets); - - let old = - Nominators::::get(&ledger.stash).map_or_else(Vec::new, |x| x.targets.into_inner()); - - let targets: BoundedVec<_, _> = targets - .into_iter() - .map(|t| T::Lookup::lookup(t).map_err(DispatchError::from)) - .map(|n| { - n.and_then(|n| { - if old.contains(&n) || !Validators::::get(&n).blocked { - Ok(n) - } else { - Err(Error::::BadTarget.into()) - } - }) - }) - .collect::, _>>()? - .try_into() - .map_err(|_| Error::::TooManyNominators)?; - - Ok((ledger.stash, targets)) - } - - pub(crate) fn do_unchecked_nominate_writes( - stash: &T::AccountId, - targets: BoundedVec, - ) { - let nominations = Nominations { - targets, - // Initial nominations are considered submitted at era 0. See `Nominations` doc - submitted_in: Self::current_era().unwrap_or(0), - suppressed: false, - }; - - Self::do_remove_validator(&stash); - Self::do_add_nominator(&stash, nominations); - } } impl ElectionDataProvider for Pallet { @@ -1418,19 +1339,6 @@ impl StakingInterface for Pallet { Self::ledger(controller).map(|l| l.active) } - fn can_bond_extra(controller: &Self::AccountId, extra: Self::Balance) -> bool { - let ledger = match Self::ledger(&controller) { - Some(l) => l, - None => return false, - }; - - if ledger.active.saturating_add(extra) < T::Currency::minimum_balance() { - false - } else { - true - } - } - fn bond_extra(stash: Self::AccountId, extra: Self::Balance) -> DispatchResult { Self::bond_extra(RawOrigin::Signed(stash).into(), extra) } @@ -1452,20 +1360,6 @@ impl StakingInterface for Pallet { .map_err(|err_with_post_info| err_with_post_info.error) } - fn bond_checks( - stash: &Self::AccountId, - controller: &Self::AccountId, - value: Self::Balance, - _: &Self::AccountId, - ) -> Result<(), DispatchError> { - Self::do_bond_checks(stash, controller, value)?; - if frame_system::Pallet::::can_inc_consumer(stash) { - Ok(()) - } else { - Err(Error::::BadState.into()) - } - } - fn bond( stash: Self::AccountId, controller: Self::AccountId, @@ -1480,18 +1374,10 @@ impl StakingInterface for Pallet { ) } - fn nominate_checks( - controller: &Self::AccountId, + fn nominate( + controller: Self::AccountId, targets: Vec, - ) -> Result<(Self::AccountId, Vec), DispatchError> { - Self::do_nominate_checks(controller, targets) - .map(|(stash, targets)| (stash, targets.into_inner())) - } - - fn unchecked_nominate(stash: &Self::AccountId, targets: Vec) { - Self::do_unchecked_nominate_writes( - stash, - targets.try_into().expect("the caller only inputs valid inputs. qed."), - ); + ) -> DispatchResult { + Self::nominate(RawOrigin::Signed(controller).into(), targets) } } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index de83b1500be35..7d94bc38cfed8 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -739,9 +739,21 @@ pub mod pallet { payee: RewardDestination, ) -> DispatchResult { let stash = ensure_signed(origin)?; + + if >::contains_key(&stash) { + Err(Error::::AlreadyBonded)? + } + let controller = T::Lookup::lookup(controller)?; - Self::do_bond_checks(&stash, &controller, value)?; + if >::contains_key(&controller) { + Err(Error::::AlreadyPaired)? + } + + // Reject a bond which is considered to be _dust_. + if value < T::Currency::minimum_balance() { + Err(Error::::InsufficientBond)? + } frame_system::Pallet::::inc_consumers(&stash).map_err(|_| Error::::BadState)?; @@ -993,8 +1005,54 @@ pub mod pallet { targets: Vec<::Source>, ) -> DispatchResult { let controller = ensure_signed(origin)?; - let (stash, targets) = Self::do_nominate_checks(&controller, targets)?; - Self::do_unchecked_nominate_writes(&stash, targets); + + let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + ensure!(ledger.active >= MinNominatorBond::::get(), Error::::InsufficientBond); + let stash = &ledger.stash; + + // Only check limits if they are not already a nominator. + if !Nominators::::contains_key(stash) { + // If this error is reached, we need to adjust the `MinNominatorBond` and start + // calling `chill_other`. Until then, we explicitly block new nominators to protect + // the runtime. + if let Some(max_nominators) = MaxNominatorsCount::::get() { + ensure!( + Nominators::::count() < max_nominators, + Error::::TooManyNominators + ); + } + } + + ensure!(!targets.is_empty(), Error::::EmptyTargets); + ensure!(targets.len() <= T::MaxNominations::get() as usize, Error::::TooManyTargets); + + let old = Nominators::::get(stash).map_or_else(Vec::new, |x| x.targets.into_inner()); + + let targets: BoundedVec<_, _> = targets + .into_iter() + .map(|t| T::Lookup::lookup(t).map_err(DispatchError::from)) + .map(|n| { + n.and_then(|n| { + if old.contains(&n) || !Validators::::get(&n).blocked { + Ok(n) + } else { + Err(Error::::BadTarget.into()) + } + }) + }) + .collect::, _>>()? + .try_into() + .map_err(|_| Error::::TooManyNominators)?; + + let nominations = Nominations { + targets, + // Initial nominations are considered submitted at era 0. See `Nominations` doc. + submitted_in: Self::current_era().unwrap_or(0), + suppressed: false, + }; + + Self::do_remove_validator(stash); + Self::do_add_nominator(stash, nominations); Ok(()) } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 3ce0a8f7f55c9..87450636f0b2c 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4716,181 +4716,6 @@ mod staking_interface { use super::*; use sp_staking::StakingInterface as _; - #[test] - fn can_nominate_passes_valid_inputs() { - ExtBuilder::default().build_and_execute(|| { - assert_ok!(Staking::bond(Origin::signed(81), 80, 1, RewardDestination::Controller)); - - let targets = vec![11]; - // First time nominating - assert_eq!( - Staking::nominate_checks(&80, targets.clone()).unwrap(), - (81, targets.clone()) - ); - - // Now actually nominate - assert_ok!(Staking::nominate(Origin::signed(80), targets.clone())); - - // 11 blocks - assert_ok!(Staking::validate( - Origin::signed(10), - ValidatorPrefs { blocked: true, ..Default::default() } - )); - // but we can still re-nominate them since they are in the old nominations - assert_eq!( - Staking::nominate_checks(&80, targets.clone()).unwrap(), - (81, targets.clone()) - ); - - // The nominator count limit is set to 0 - MaxNominatorsCount::::set(Some(0)); - // but we can still nominate because we have pre-existing nomination - assert_eq!( - Staking::nominate_checks(&80, targets.clone()).unwrap(), - (81, targets.clone()) - ); - - // They can nominate with exactly `MAX_NOMINATIONS` targets - let targets: Vec<_> = (0..MaxNominations::get()).map(|i| i as u64).collect(); - assert_eq!( - Staking::nominate_checks(&80, targets.clone()).unwrap(), - (81, targets.clone()) - ); - }); - } - - #[test] - fn nominate_checks_fails_invalid_inputs() { - ExtBuilder::default() - .existential_deposit(100) - .balance_factor(100) - .min_nominator_bond(1_000) - .build_and_execute(|| { - let targets = vec![11]; - - // Not bonded, so no ledger - assert_noop!( - Staking::nominate_checks(&80, targets.clone()), - Error::::NotController - ); - - // Bonded, but below min bond to nominate - assert_ok!(Staking::bond( - Origin::signed(81), - 80, - 1_000 - 10, - RewardDestination::Controller - )); - assert_noop!( - Staking::nominate_checks(&80, targets.clone()), - Error::::InsufficientBond - ); - - // Meets min bond, but already at nominator limit - assert_ok!(Staking::bond( - Origin::signed(71), - 70, - 1_000 + 10, - RewardDestination::Controller - )); - MaxNominatorsCount::::set(Some(0)); - assert_noop!( - Staking::nominate_checks(&70, targets.clone()), - Error::::TooManyNominators - ); - MaxNominatorsCount::::set(None); - - // Targets are empty - let targets = vec![]; - assert_noop!( - Staking::nominate_checks(&70, targets.clone()), - Error::::EmptyTargets - ); - - // Too many targets - let targets: Vec<_> = (0..=MaxNominations::get()).map(|i| i as u64).collect(); - assert_noop!( - Staking::nominate_checks(&70, targets.clone()), - Error::::TooManyTargets - ); - - // A target is blocking - assert_ok!(Staking::validate( - Origin::signed(10), - ValidatorPrefs { blocked: true, ..Default::default() } - )); - let targets = vec![11]; - assert_noop!( - Staking::nominate_checks(&70, targets.clone()), - Error::::BadTarget - ); - }); - } - - #[test] - fn bond_checks_works() { - ExtBuilder::default() - .existential_deposit(100) - .balance_factor(100) - .nominate(false) - .build_and_execute(|| { - // Amount to bond does not meet ED - assert_noop!( - Staking::bond_checks(&61, &60, 99, &0), - Error::::InsufficientBond - ); - - // A ledger already exists for the given controller - Ledger::::insert(60, StakingLedger::default_from(61)); - assert_noop!(Staking::bond_checks(&61, &60, 100, &0), Error::::AlreadyPaired); - - // The stash is already bonded to a controller - Bonded::::insert(71, 70); - assert_noop!(Staking::bond_checks(&71, &70, 100, &0), Error::::AlreadyBonded); - - // Cannot increment consumers for stash - frame_system::Account::::mutate(&81, |a| { - a.providers = 0; - }); - assert_noop!(Staking::bond_checks(&81, &80, 100, &0), Error::::BadState); - - // Works with valid inputs - assert_ok!(Staking::bond_checks(&101, &100, 100, &0)); - - // Clean up so test harness post checks work - Ledger::::remove(60); - Bonded::::remove(71); - }); - } - - #[test] - fn can_bond_extra_works() { - ExtBuilder::default() - .existential_deposit(100) - .balance_factor(100) - .build_and_execute(|| { - // The account is not bonded in the first place - assert_eq!(Staking::can_bond_extra(&60, 100), false); - - // The account is bonded - assert_ok!(Staking::bond( - Origin::signed(61), - 60, - 100, - RewardDestination::Controller - )); - // but the active balance of the account is too low - Ledger::::mutate(60, |l| l.as_mut().unwrap().active = 50); - assert_eq!(Staking::can_bond_extra(&60, 49), false); - - // The account is bonded and will have an ok active balance - assert!(Staking::can_bond_extra(&60, 50)); - - // Clean up so post checks pass - Ledger::::mutate(60, |l| l.as_mut().unwrap().active = 100); - }); - } - // TODO: probably should test all other fns of the interface impl? Although benchmarks should // at least make sure those work on the happy path } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index a0f53e59626ad..c03a2b1d898ec 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -121,5 +121,5 @@ pub trait StakingInterface { payee: Self::AccountId, ) -> DispatchResult; - fn nominate(stash: &Self::AccountId, targets: Vec); + fn nominate(stash: Self::AccountId, targets: Vec) -> DispatchResult; } From 01ec9a9ceaae01d5ef1925482347419fa3e5c13f Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sat, 5 Feb 2022 16:10:15 -0800 Subject: [PATCH 077/299] Rename to pallet-nomination-pools --- Cargo.lock | 38 +++++++++---------- Cargo.toml | 2 +- frame/{pools => nomination-pools}/Cargo.toml | 2 +- frame/{pools => nomination-pools}/src/lib.rs | 6 +-- frame/{pools => nomination-pools}/src/mock.rs | 0 .../{pools => nomination-pools}/src/tests.rs | 0 frame/staking/src/pallet/impls.rs | 5 +-- 7 files changed, 25 insertions(+), 28 deletions(-) rename frame/{pools => nomination-pools}/Cargo.toml (97%) rename frame/{pools => nomination-pools}/src/lib.rs (99%) rename frame/{pools => nomination-pools}/src/mock.rs (100%) rename frame/{pools => nomination-pools}/src/tests.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 711f323512e80..82df22ad4a022 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6016,8 +6016,8 @@ dependencies = [ ] [[package]] -name = "pallet-offences" -version = "4.0.0-dev" +name = "pallet-nomination-pools" +version = "0.0.1" dependencies = [ "frame-support", "frame-system", @@ -6025,33 +6025,26 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "serde", + "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", "sp-staking", "sp-std", + "sp-tracing", ] [[package]] -name = "pallet-offences-benchmarking" +name = "pallet-offences" version = "4.0.0-dev" dependencies = [ - "frame-benchmarking", - "frame-election-provider-support", "frame-support", "frame-system", - "pallet-babe", + "log 0.4.14", "pallet-balances", - "pallet-grandpa", - "pallet-im-online", - "pallet-offences", - "pallet-session", - "pallet-staking", - "pallet-staking-reward-curve", - "pallet-timestamp", "parity-scale-codec", "scale-info", + "serde", "sp-core", "sp-io", "sp-runtime", @@ -6060,22 +6053,29 @@ dependencies = [ ] [[package]] -name = "pallet-pools" -version = "0.0.1" +name = "pallet-offences-benchmarking" +version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "pallet-babe", "pallet-balances", + "pallet-grandpa", + "pallet-im-online", + "pallet-offences", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", "sp-staking", "sp-std", - "sp-tracing", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 70a06bda4414d..a99c8c339f545 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,7 @@ members = [ "frame/offences", "frame/preimage", "frame/proxy", - "frame/pools", + "frame/nomination-pools", "frame/randomness-collective-flip", "frame/recovery", "frame/scheduler", diff --git a/frame/pools/Cargo.toml b/frame/nomination-pools/Cargo.toml similarity index 97% rename from frame/pools/Cargo.toml rename to frame/nomination-pools/Cargo.toml index c46b1be51e757..210424196fd13 100644 --- a/frame/pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pallet-pools" +name = "pallet-nomination-pools" version = "0.0.1" authors = ["Parity Technologies "] edition = "2021" diff --git a/frame/pools/src/lib.rs b/frame/nomination-pools/src/lib.rs similarity index 99% rename from frame/pools/src/lib.rs rename to frame/nomination-pools/src/lib.rs index 2c99a68517490..87a4087fee110 100644 --- a/frame/pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1,4 +1,4 @@ -//! # Delegation pools for nominating +//! # Nomination Pools for Staking Delegation //! //! This pallet allows delegators to delegate their stake to nominating pools, each of which acts as //! a nominator and nominates validators on their behalf. @@ -248,7 +248,7 @@ use frame_support::{ ensure, pallet_prelude::*, storage::bounded_btree_map::BoundedBTreeMap, - traits::{Currency, DefensiveResult, ExistenceRequirement, Get, DefensiveOption}, + traits::{Currency, DefensiveOption, DefensiveResult, ExistenceRequirement, Get}, DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; @@ -905,7 +905,7 @@ pub mod pallet { amount, reward_account.clone(), )?; - T::StakingInterface::nominate(pool_account.clone(), validators); + T::StakingInterface::nominate(pool_account.clone(), validators)?; Delegators::::insert( who, diff --git a/frame/pools/src/mock.rs b/frame/nomination-pools/src/mock.rs similarity index 100% rename from frame/pools/src/mock.rs rename to frame/nomination-pools/src/mock.rs diff --git a/frame/pools/src/tests.rs b/frame/nomination-pools/src/tests.rs similarity index 100% rename from frame/pools/src/tests.rs rename to frame/nomination-pools/src/tests.rs diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 761a775b6c1e2..fb80d50cf0362 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1374,10 +1374,7 @@ impl StakingInterface for Pallet { ) } - fn nominate( - controller: Self::AccountId, - targets: Vec, - ) -> DispatchResult { + fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult { Self::nominate(RawOrigin::Signed(controller).into(), targets) } } From 8843280cfc6110aebc7de7de2c615b9b4069f35d Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sat, 5 Feb 2022 16:27:20 -0800 Subject: [PATCH 078/299] Make staking ledger generic over T --- frame/staking/src/lib.rs | 82 +++++++++++++++---------------- frame/staking/src/pallet/impls.rs | 5 +- frame/staking/src/pallet/mod.rs | 3 +- frame/staking/src/slashing.rs | 7 +-- 4 files changed, 43 insertions(+), 54 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 7d01f01cef98a..065398fe7e0f3 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -435,30 +435,29 @@ pub struct UnlockChunk { /// The ledger of a (bonded) stash. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct StakingLedger { +#[scale_info(skip_type_params(T))] +pub struct StakingLedger { /// The stash account whose balance is actually locked and at stake. - pub stash: AccountId, + pub stash: T::AccountId, /// The total amount of the stash's balance that we are currently accounting for. /// It's just `active` plus all the `unlocking` balances. #[codec(compact)] - pub total: Balance, + pub total: BalanceOf, /// The total amount of the stash's balance that will be at stake in any forthcoming /// rounds. #[codec(compact)] - pub active: Balance, + pub active: BalanceOf, /// Any balance that is becoming free, which may eventually be transferred out /// of the stash (assuming it doesn't get slashed first). - pub unlocking: Vec>, + pub unlocking: Vec>>, /// List of eras for which the stakers behind a validator have claimed rewards. Only updated /// for validators. pub claimed_rewards: Vec, } -impl - StakingLedger -{ +impl StakingLedger { /// Initializes the default object using the given `validator`. - pub fn default_from(stash: AccountId) -> Self { + pub fn default_from(stash: T::AccountId) -> Self { Self { stash, total: Zero::zero(), @@ -497,8 +496,8 @@ impl (Self, Balance) { - let mut unlocking_balance: Balance = Zero::zero(); + fn rebond(mut self, value: BalanceOf) -> (Self, BalanceOf) { + let mut unlocking_balance = BalanceOf::::zero(); while let Some(last) = self.unlocking.last_mut() { if unlocking_balance + last.value <= value { @@ -520,29 +519,23 @@ impl StakingLedger -where - Balance: AtLeast32BitUnsigned + Saturating + Copy, - AccountId: Clone, -{ /// Slash the staker for a given amount of balance. This can grow the value /// of the slash in the case that the staker has less than `minimum_balance` /// active funds. Returns the amount of funds actually slashed. /// /// Slashes from `active` funds first, and then `unlocking`, starting with the /// chunks that are closest to unlocking. - fn slash>( + fn slash( &mut self, - value: Balance, - minimum_balance: Balance, + value: BalanceOf, + minimum_balance: BalanceOf, slash_era: EraIndex, active_era: EraIndex, - ) -> Balance { - use sp_staking::{SlashPoolArgs, SlashPoolOut}; + ) -> BalanceOf { + use sp_staking::{PoolsInterface as _, SlashPoolArgs, SlashPoolOut}; if let Some(SlashPoolOut { slashed_bonded, slashed_unlocking }) = - P::slash_pool(SlashPoolArgs { + T::PoolsInterface::slash_pool(SlashPoolArgs { pool_stash: &self.stash, slash_amount: value, slash_era, @@ -560,10 +553,10 @@ where /// Slash a pool account fn pool_slash( &mut self, - new_active: Balance, - new_chunk_balances: BTreeMap, - ) -> Balance { - let mut total_slashed = Balance::zero(); + new_active: BalanceOf, + new_chunk_balances: BTreeMap>, + ) -> BalanceOf { + let mut total_slashed = BalanceOf::::zero(); // Modify the unlocking chunks in place for chunk in &mut self.unlocking { @@ -585,28 +578,33 @@ where } // Slash a validator or nominator's stash - fn standard_slash(&mut self, mut value: Balance, minimum_balance: Balance) -> Balance { + fn standard_slash( + &mut self, + mut value: BalanceOf, + minimum_balance: BalanceOf, + ) -> BalanceOf { let pre_total = self.total; let total = &mut self.total; let active = &mut self.active; - let slash_out_of = - |total_remaining: &mut Balance, target: &mut Balance, value: &mut Balance| { - let mut slash_from_target = (*value).min(*target); - - if !slash_from_target.is_zero() { - *target -= slash_from_target; + let slash_out_of = |total_remaining: &mut BalanceOf, + target: &mut BalanceOf, + value: &mut BalanceOf| { + let mut slash_from_target = (*value).min(*target); - // Don't leave a dust balance in the staking system. - if *target <= minimum_balance { - slash_from_target += *target; - *value += sp_std::mem::replace(target, Zero::zero()); - } + if !slash_from_target.is_zero() { + *target -= slash_from_target; - *total_remaining = total_remaining.saturating_sub(slash_from_target); - *value -= slash_from_target; + // Don't leave a dust balance in the staking system. + if *target <= minimum_balance { + slash_from_target += *target; + *value += sp_std::mem::replace(target, Zero::zero()); } - }; + + *total_remaining = total_remaining.saturating_sub(slash_from_target); + *value -= slash_from_target; + } + }; slash_out_of(total, active, &mut value); diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index fb80d50cf0362..a44507fa4fb7a 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -201,10 +201,7 @@ impl Pallet { /// Update the ledger for a controller. /// /// This will also update the stash lock. - pub(crate) fn update_ledger( - controller: &T::AccountId, - ledger: &StakingLedger>, - ) { + pub(crate) fn update_ledger(controller: &T::AccountId, ledger: &StakingLedger) { T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); >::insert(controller, ledger); } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 7d94bc38cfed8..a2972cbf6a886 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -220,8 +220,7 @@ pub mod pallet { /// Map from all (unlocked) "controller" accounts to the info regarding the staking. #[pallet::storage] #[pallet::getter(fn ledger)] - pub type Ledger = - StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>>; + pub type Ledger = StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>; /// Where the reward payment should be made. Keyed by stash. #[pallet::storage] diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 22f867151c85d..0a536a92e161e 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -611,12 +611,7 @@ pub fn do_slash( None => return, // nothing to do. }; - let value = ledger.slash::( - value, - T::Currency::minimum_balance(), - slash_era, - apply_era, - ); + let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era, apply_era); if !value.is_zero() { let (imbalance, missing) = T::Currency::slash(stash, value); From dbb2c0bb2e9c1ed8829e20b12a5c53da4d9cc700 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sat, 5 Feb 2022 16:32:42 -0800 Subject: [PATCH 079/299] Make update_total_earnings_and_balance take a ref --- frame/nomination-pools/src/lib.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 87a4087fee110..6f235bd36f9a2 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -441,15 +441,14 @@ pub struct RewardPool { } impl RewardPool { - fn update_total_earnings_and_balance(mut self) -> Self { + /// Mutate the reward pool by updating the total earnings and current free balance. + fn update_total_earnings_and_balance(&mut self) { let current_balance = T::Currency::free_balance(&self.account); // The earnings since the last time it was updated let new_earnings = current_balance.saturating_sub(self.balance); // The lifetime earnings of the of the reward pool self.total_earnings = new_earnings.saturating_add(self.total_earnings); self.balance = current_balance; - - self } } @@ -683,10 +682,10 @@ pub mod pallet { // We don't actually care about writing the reward pool, we just need its // total earnings at this point in time. - let reward_pool = RewardPools::::get(&pool_account) - .defensive_ok_or_else(|| Error::::RewardPoolNotFound)? - // This is important because we want the most up-to-date total earnings. - .update_total_earnings_and_balance(); + let mut reward_pool = RewardPools::::get(&pool_account) + .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; + // This is important because we want the most up-to-date total earnings. + reward_pool.update_total_earnings_and_balance(); let old_free_balance = T::Currency::free_balance(&pool_account); // Transfer the funds to be bonded from `who` to the pools account so the pool can then @@ -965,7 +964,7 @@ impl Pallet { /// Calculate the rewards for `delegator`. fn calculate_delegator_payout( bonded_pool: &BondedPool, - reward_pool: RewardPool, + mut reward_pool: RewardPool, mut delegator: Delegator, ) -> Result<(RewardPool, Delegator, BalanceOf), DispatchError> { // If the delegator is unbonding they cannot claim rewards. Note that when the delagator @@ -973,7 +972,7 @@ impl Pallet { ensure!(delegator.unbonding_era.is_none(), Error::::AlreadyUnbonding); let last_total_earnings = reward_pool.total_earnings; - let mut reward_pool = reward_pool.update_total_earnings_and_balance(); + reward_pool.update_total_earnings_and_balance(); // Notice there is an edge case where total_earnings have not increased and this is zero let new_earnings = T::BalanceToU256::convert( reward_pool.total_earnings.saturating_sub(last_total_earnings), From 3f0bbcffc67509ea11c274af71be1647c31fd8df Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sat, 5 Feb 2022 16:39:10 -0800 Subject: [PATCH 080/299] Clean up join transfer logic --- frame/nomination-pools/src/lib.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 6f235bd36f9a2..ffe97e21c6078 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -687,23 +687,16 @@ pub mod pallet { // This is important because we want the most up-to-date total earnings. reward_pool.update_total_earnings_and_balance(); - let old_free_balance = T::Currency::free_balance(&pool_account); // Transfer the funds to be bonded from `who` to the pools account so the pool can then // go bond them. T::Currency::transfer(&who, &pool_account, amount, ExistenceRequirement::KeepAlive)?; - - // This should now include the transferred balance. - let new_free_balance = T::Currency::free_balance(&pool_account); - // Get the exact amount we can bond extra. - let exact_amount_to_bond = new_free_balance.saturating_sub(old_free_balance); // We must calculate the points to issue *before* we bond `who`'s funds, else the // points:balance ratio will be wrong. - let new_points = bonded_pool.issue(exact_amount_to_bond); - + let new_points = bonded_pool.issue(amount); // The pool should always be created in such a way its in a state to bond extra, but if // the active balance is slashed below the minimum bonded or the account cannot be // found, we exit early. - T::StakingInterface::bond_extra(pool_account.clone(), exact_amount_to_bond)?; + T::StakingInterface::bond_extra(pool_account.clone(), amount)?; Delegators::insert( who.clone(), @@ -725,7 +718,7 @@ pub mod pallet { Self::deposit_event(Event::::Joined { delegator: who, pool: pool_account, - bonded: exact_amount_to_bond, + bonded: amount, }); Ok(()) From ed41b7ee63bc784d0983d4b8a4f8aa46e04df3a9 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sat, 5 Feb 2022 17:11:33 -0800 Subject: [PATCH 081/299] Make the top level docs passive --- frame/nomination-pools/src/lib.rs | 178 +++++++++++++++--------------- 1 file changed, 92 insertions(+), 86 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index ffe97e21c6078..e3e619132afef 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -12,23 +12,21 @@ //! The delegation pool abstraction is composed of: //! //! * bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and -//! [`BondedPools`] +//! [`BondedPoolPoints`] //! * reward pool: Tracks rewards earned by actively staked funds. See [`RewardPool`] and //! [`RewardPools`]. -//! * unbonding sub pools: This a collection of pools at different phases of the unbonding -//! lifecycle. See [`SubPools`] and [`SubPoolsStorage`]. +//! * unbonding sub pools: Collection of pools at different phases of the unbonding lifecycle. See +//! [`SubPools`] and [`SubPoolsStorage`]. //! * delegators: Accounts that are members of pools. See [`Delegator`] and [`Delegators`]. -//! -//! In order to maintain scalability, all operations are independent of the number of delegators. To -//! do this, we store delegation specific information local to the delegator while the pool data -//! structures have bounded datum . -//! -//! ### Design goals -//! -//! * Maintain integrity of slashing events, sufficiently penalizing delegators that where in the -//! pool while it was backing a validator that got slashed. -//! * Maximize scalability in terms of delegator count. -//! +// In order to maintain scalability, all operations are independent of the number of delegators. To +// do this, we store delegation specific information local to the delegator while the pool data +// structures have bounded datum. +// +// ### Design goals +// +// * Maintain integrity of slashing events, sufficiently penalizing delegators that where in the +// pool while it was backing a validator that got slashed. +// * Maximize scalability in terms of delegator count. //! ### Bonded pool //! //! A bonded pool nominates with its total balance, excluding that which has been withdrawn for @@ -36,8 +34,8 @@ //! delegation members. A bonded pool tracks its points and reads its bonded balance. //! //! When a delegator joins a pool, `amount_transferred` is transferred from the delegators account -//! to the bonded pools account. Then the pool calls `bond_extra(amount_transferred)` and issues new -//! points which are tracked by the delegator and added to the bonded pools points. +//! to the bonded pools account. Then the pool calls `staking::bond_extra(amount_transferred)` and +//! issues new points which are tracked by the delegator and added to the bonded pool's points. //! //! When the pool already has some balance, we want the value of a point before the transfer to //! equal the value of a point after the transfer. So, when a delegator joins a bonded pool with a @@ -83,22 +81,29 @@ //! When a delegator initiates a claim, the following happens: //! //! 1) Compute the reward pool's total points and the delegator's virtual points in the reward pool -//! * We first compute `current_total_earnings` (`current_balance` is the free balance of the -//! reward pool at the beginning of these operations.) ``` current_total_earnings = -//! current_balance - reward_pool.balance + pool.total_earnings; ``` -//! * Then we compute the `current_points`. Every balance unit that was added to the reward pool -//! since last time recorded means that we increase the `pool.points` by +//! * First `current_total_earnings` is computed (`current_balance` is the free balance of the +//! reward pool at the beginning of these operations.) +//! ``` +//! current_total_earnings = +//! current_balance - reward_pool.balance + pool.total_earnings; +//! ``` +//! * Then the `current_points` is computed. Every balance unit that was added to the reward +//! pool since last time recorded means that the `pool.points` is increased by //! `bonding_pool.total_points`. In other words, for every unit of balance that has been -//! earned by the reward pool, we inflate the reward pool points by `bonded_pool.points`. In +//! earned by the reward pool, the reward pool points are inflated by `bonded_pool.points`. In //! effect this allows each, single unit of balance (e.g. planck) to be divvied up pro-rata -//! among delegators based on points. ``` new_earnings = current_total_earnings - -//! reward_pool.total_earnings; current_points = reward_pool.points + bonding_pool.points * -//! new_earnings; ``` -//! * Finally, we compute `delegator_virtual_points`: the product of the delegator's points in -//! the bonding pool and the total inflow of balance units since the last time the delegator -//! claimed rewards ``` new_earnings_since_last_claim = current_total_earnings - -//! delegator.reward_pool_total_earnings; delegator_virtual_points = delegator.points * -//! new_earnings_since_last_claim; ``` +//! among delegators based on points. +//! ``` +//! new_earnings = current_total_earnings - reward_pool.total_earnings; +//! current_points = reward_pool.points + bonding_pool.points * new_earnings; +//! ``` +//! * Finally, the`delegator_virtual_points` are computed: the product of the delegator's points +//! in the bonding pool and the total inflow of balance units since the last time the +//! delegator claimed rewards +//! ``` +//! new_earnings_since_last_claim = current_total_earnings - delegator.reward_pool_total_earnings; +//! delegator_virtual_points = delegator.points * new_earnings_since_last_claim; +//! ``` //! 2) Compute the `delegator_payout`: //! ``` //! delegator_pool_point_ratio = delegator_virtual_points / current_points; @@ -117,13 +122,13 @@ //! ``` //! //! _Note_: One short coming of this design is that new joiners can claim rewards for the era after -//! they join in even though their funds did not contribute to the pools vote weight. When a -//! delegator joins, they set the field `reward_pool_total_earnings` equal to the `total_earnings` +//! they join even though their funds did not contribute to the pools vote weight. When a +//! delegator joins, it's `reward_pool_total_earnings` field is set equal to the `total_earnings` //! of the reward pool at that point in time. At best the reward pool has the rewards up through the -//! previous era. If the delegator joins prior to the election snapshot they will benefit from the -//! rewards for the current era despite not contributing to the pool's vote weight. If they join -//! after the election snapshot is taken they will benefit from the rewards of the next _2_ eras -//! because their vote weight will not be counted until the election snapshot in current era + 1. +//! previous era. If a delegator joins prior to the election snapshot it will benefit from the +//! rewards for the active era despite not contributing to the pool's vote weight. If it joins +//! after the election snapshot is taken it will benefit from the rewards of the next _2_ eras +//! because it's vote weight will not be counted until the election snapshot in active era + 1. //! //! **Relevant extrinsics:** //! @@ -131,31 +136,31 @@ //! //! ### Unbonding sub pools //! -//! When a delegator unbonds, their balance is unbonded in the bonded pool's account and tracked in +//! When a delegator unbonds, it's balance is unbonded in the bonded pool's account and tracked in //! an unbonding pool associated with the active era. If no such pool exists, one is created. To -//! track which unbonding sub pool a delegator belongs too, the delegator tracks their +//! track which unbonding sub pool a delegator belongs too, a delegator tracks it's //! `unbonding_era`. //! -//! When a delegator initiates unbonding we calculate their claim on the bonded pool -//! (`balance_to_unbond`) as: +//! When a delegator initiates unbonding it's claim on the bonded pool +//! (`balance_to_unbond`) is computed as: //! //! ``` //! balance_to_unbond = (bonded_pool.balance / bonded_pool.points) * delegator.points; //! ``` -//! If this is the first transfer into the unbonding pool we can issue an arbitrary amount of points -//! per balance. In this implementation we initialize unbonding pools with a 1 point to 1 balance +//! +//! If this is the first transfer into an unbonding pool arbitrary amount of points can be issued +//! per balance. In this implementation unbonding pools are initialized with a 1 point to 1 balance //! ratio (see [`POINTS_TO_BALANCE_INIT_RATIO`]). Otherwise, the unbonding pools hold the same -//! points to balance ratio properties as the bonded pool, so we issue the delegator points in the -//! unbonding pool based on +//! points to balance ratio properties as the bonded pool, so delegator points in the +//! unbonding pool are issued based on //! //! ``` //! new_points_issued = (points_before_transfer / balance_before_transfer) * balance_to_unbond; //! ``` //! -//! For scalability, we maintain a bound on the number of unbonding sub pools (see [`MaxUnboning`]). -//! Once we reach this bound the oldest unbonding pool is merged into the new unbonded pool (see -//! `no_era` field in [`SubPools`]). In other words, a unbonding pool is removed once its older than -//! `current_era - TotalUnbondingPools`. We merge an unbonding pool into the unbonded pool with +//! For scalability, a bound is maintained on the number of unbonding sub pools (see +//! [`TotalUnbondingPools`]). An unbonding pool is removed once its older than `current_era - +//! TotalUnbondingPools`. An unbonding pool is merged into the unbonded pool with //! //! ``` //! unbounded_pool.balance = unbounded_pool.balance + unbonding_pool.balance; @@ -164,41 +169,41 @@ //! //! This scheme "averages" out the points value in the unbonded pool. //! -//! Once the delgators `unbonding_era` is older than `current_era - -//! [sp_staking::StakingInterface::bonding_duration]`, they can can cash their points out of the -//! corresponding unbonding pool. If their `unbonding_era` is older than the oldest unbonding pool -//! they cash their points out of the unbonded pool. +//! Once a delgators `unbonding_era` is older than `current_era - +//! [sp_staking::StakingInterface::bonding_duration]`, it can can cash it's points out of the +//! corresponding unbonding pool. If it's `unbonding_era` is older than `current_era - +//! TotalUnbondingPools`, it can cash it's points from the unbonded pool. //! //! **Relevant extrinsics //! //! * [`Call::unbond`] -//! * [`Call::with_unbonded`] +//! * [`Call::withdraw_unbonded`] //! //! ### Slashing //! -//! We distribute slashes evenly across the bonded pool and the unbonding pools from slash era+1 +//! Slashes are distributed evenly across the bonded pool and the unbonding pools from slash era+1 //! through the slash apply era. -//! -//! To compute and execute the slashes: -//! -//! 1) Sum up the balances of the bonded pool and the unbonding pools in range `slash_era + -//! 1..=apply_era` and store that in `total_balance_affected`. 2) Compute `slash_ratio` as -//! `slash_amount / total_balance_affected`. 3) Compute `bonded_pool_balance_after_slash` as `(1- -//! slash_ratio) * bonded_pool_balance`. 4) For all `unbonding_pool` in range `slash_era + -//! 1..=apply_era` set their balance to `(1 - slash_ratio) * unbonding_pool_balance`. -//! -//! We need to slash the relevant unbonding pools to ensure all nominators whom where in the pool -//! while it was backing a validator that equivocated are punished. Without these measures a -//! nominator could unbond right after a validator equivocated with no consequences. -//! -//! This strategy is unfair to delegators who joined after the slash, because they get slashed as -//! well, but spares delegators who unbond. The latter is much more important for security: if a -//! pool's validators are attacking the network, we want their delegators to unbond fast! Avoiding -//! slashes gives them an incentive to do that if validators get repeatedly slashed. -//! -//! To be fair to joiners, we would also need joining pools, which are actively staking, in addition -//! to the unbonding pools. For maintenance simplicity these are not implemented. -//! +// +// Slashes are computed and executed by: +// +// 1) Balances of the bonded pool and the unbonding pools in range `slash_era + +// 1..=apply_era` are summed and stored in `total_balance_affected`. +// 2) `slash_ratio` is computed as `slash_amount / total_balance_affected`. +// 3) `bonded_pool_balance_after_slash`is computed as `(1- slash_ratio) * bonded_pool_balance`. +// 4) For all `unbonding_pool` in range `slash_era + 1..=apply_era` set their balance to `(1 - +// slash_ratio) * unbonding_pool_balance`. +// +// Unbonding pools need to be slashed to ensure all nominators whom where in the bonded pool +// while it was backing a validator that equivocated are punished. Without these measures a +// nominator could unbond right after a validator equivocated with no consequences. +// +// This strategy is unfair to delegators who joined after the slash, because they get slashed as +// well, but spares delegators who unbond. The latter is much more important for security: if a +// pool's validators are attacking the network, their delegators need to unbond fast! Avoiding +// slashes gives them an incentive to do that if validators get repeatedly slashed. +// +// To be fair to joiners, this implementation also need joining pools, which are actively staking, +// in addition to the unbonding pools. For maintenance simplicity these are not implemented. //! ### Limitations //! //! * Delegators cannot vote with their staked funds because they are transferred into the pools @@ -207,21 +212,22 @@ //! * Delegators cannot quickly transfer to another pool if they do no like nominations, instead //! they must wait for the unbonding duration. //! -//! # Pool creation and upkeep -//! -//! TBD - possible options: -//! * Pools can be created by anyone but nominations can never be updated -//! * Pools can be created by anyone and the creator can update the validators -//! * Pools are created by governance and governance can update the validators -//! ... Other ideas -//! * pools can have different roles assigned: creator (puts deposit down, cannot remove deposit -//! until pool is empty), admin (can control who is nominator, destroyer and can do -//! nominate/destroy), nominator (can adjust nominators), destroyer (can initiate destroy), etc -//! //! # Runtime builder warnings //! //! * watch out for overflow of [`RewardPoints`] and [`BalanceOf`] types. Consider things like the //! chains total issuance, staking reward rate, and burn rate. +//! # Pool creation and upkeep +// +// TBD - possible options: +// * Pools can be created by anyone but nominations can never be updated +// * Pools can be created by anyone and the creator can update the validators +// * Pools are created by governance and governance can update the validators +// ... Other ideas +// * pools can have different roles assigned: creator (puts deposit down, cannot remove deposit +// until pool is empty), admin (can control who is nominator, destroyer and can do +// nominate/destroy), nominator (can adjust nominators), destroyer (can initiate destroy), etc +// + // Invariants // - A `delegator.pool` must always be a valid entry in `RewardPools`, and `BondedPoolPoints`. // - Every entry in `BondedPoolPoints` must have a corresponding entry in `RewardPools` From aa5b5480eaa5b1f9c3cefb4edcc8c1513799c012 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sat, 5 Feb 2022 17:52:10 -0800 Subject: [PATCH 082/299] Add and test pool_withdraw_unbonded --- frame/nomination-pools/src/lib.rs | 41 ++++++++--------- frame/nomination-pools/src/tests.rs | 70 ++++++++++++++++++----------- 2 files changed, 66 insertions(+), 45 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index e3e619132afef..d0223c145cb86 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -269,18 +269,6 @@ mod mock; mod tests; pub use pallet::*; -pub(crate) const LOG_TARGET: &'static str = "runtime::pools"; - -// Syntactic sugar for logging. -#[macro_export] -macro_rules! log { - ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { - log::$level!( - target: LOG_TARGET, - concat!("[{:?}] 💰", $patter), >::block_number() $(, $values)* - ) - }; -} type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -748,9 +736,11 @@ pub mod pallet { } /// A bonded delegator can use this to unbond _all_ funds from the pool. - /// In order to withdraw the funds, the delegator must wait + /// + /// If their are too many unlocking chunks to unbond with the pool account, + /// [`Self::withdraw_unbonded_pool`] can be called to try and minimize unlocking chunks. #[pallet::weight(666)] - pub fn unbond(origin: OriginFor, num_slashing_spans: u32) -> DispatchResult { + pub fn unbond(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; // TODO check if this is owner, if its the owner then they must be the only person in // the pool and it needs to be destroyed with withdraw_unbonded @@ -776,12 +766,7 @@ pub mod pallet { // to unbond so we have the correct points for the balance:share ratio. bonded_pool.points = bonded_pool.points.saturating_sub(delegator.points); - // Call withdraw unbonded to minimize unlocking chunks. If this is not done then we - // would have to rely on delegators calling `withdraw_unbonded` in order to clear - // unlocking chunks. This is a catch 22 for delegators who have not yet unbonded - // because the pool needs to call `withdraw_unbonded` so they can `unbond`, but they - // must call `unbond` prior to being able to call `withdraw_unbonded`. - T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; + // T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; // Unbond in the actual underlying pool T::StakingInterface::unbond(delegator.pool.clone(), balance_to_unbond)?; @@ -810,6 +795,22 @@ pub mod pallet { Ok(()) } + /// A permissionless function that allows users to call `withdraw_unbonded` for the pools + /// account. + /// + /// This is useful if their are too many unlocking chunks to unbond, and some can be cleared + /// by withdrawing. + #[pallet::weight(666)] + pub fn pool_withdraw_unbonded( + origin: OriginFor, + pool_account: T::AccountId, + num_slashing_spans: u32, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + T::StakingInterface::withdraw_unbonded(pool_account, num_slashing_spans)?; + Ok(()) + } + #[pallet::weight(666)] pub fn withdraw_unbonded(origin: OriginFor, num_slashing_spans: u32) -> DispatchResult { let who = ensure_signed(origin)?; diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 08d5224dbeccc..eb16a3183ce92 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1104,7 +1104,7 @@ mod unbond { #[test] fn unbond_pool_of_1_works() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(Pools::unbond(Origin::signed(10), 0)); + assert_ok!(Pools::unbond(Origin::signed(10))); assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, @@ -1131,7 +1131,7 @@ mod unbond { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 600); // When - assert_ok!(Pools::unbond(Origin::signed(40), 0)); + assert_ok!(Pools::unbond(Origin::signed(40))); // Then assert_eq!( @@ -1147,7 +1147,7 @@ mod unbond { assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding // When - assert_ok!(Pools::unbond(Origin::signed(10), 0)); + assert_ok!(Pools::unbond(Origin::signed(10))); // Then assert_eq!( @@ -1163,7 +1163,7 @@ mod unbond { assert_eq!(Balances::free_balance(&10), 10 + 10); // When - assert_ok!(Pools::unbond(Origin::signed(550), 0)); + assert_ok!(Pools::unbond(Origin::signed(550))); // Then assert_eq!( @@ -1200,7 +1200,7 @@ mod unbond { let current_era = 1 + TotalUnbondingPools::::get(); CurrentEra::set(current_era); - assert_ok!(Pools::unbond(Origin::signed(10), 0)); + assert_ok!(Pools::unbond(Origin::signed(10))); // Then assert_eq!( @@ -1220,7 +1220,7 @@ mod unbond { #[should_panic = "Defensive failure has been triggered!"] fn unbond_errors_correctly() { ExtBuilder::default().build_and_execute(|| { - assert_noop!(Pools::unbond(Origin::signed(11), 0), Error::::DelegatorNotFound); + assert_noop!(Pools::unbond(Origin::signed(11)), Error::::DelegatorNotFound); // Add the delegator let delegator = Delegator { @@ -1231,7 +1231,7 @@ mod unbond { }; Delegators::::insert(11, delegator); - let _ = Pools::unbond(Origin::signed(11), 0); + let _ = Pools::unbond(Origin::signed(11)); }); } @@ -1249,11 +1249,31 @@ mod unbond { let bonded_pool = BondedPool { points: 10, account: 1 }; BondedPool::::insert(bonded_pool); - let _ = Pools::unbond(Origin::signed(11), 0); + let _ = Pools::unbond(Origin::signed(11)); }); } } +mod pool_withdraw_unbonded { + use super::*; + + #[test] + fn pool_withdraw_unbonded_works() { + ExtBuilder::default().build_and_execute(|| { + // Given 10 unbond'ed directly against the pool account + assert_ok!(StakingMock::unbond(PRIMARY_ACCOUNT, 5)); + // and the pool account only has 10 balance + assert_eq!(Balances::free_balance(PRIMARY_ACCOUNT), 10); + + // When + assert_ok!(Pools::pool_withdraw_unbonded(Origin::signed(10), PRIMARY_ACCOUNT, 0)); + + // Then there unbonding balance becomes free + assert_eq!(Balances::free_balance(PRIMARY_ACCOUNT), 15); + }) + } +} + mod withdraw_unbonded { use super::*; @@ -1265,14 +1285,14 @@ mod withdraw_unbonded { // Given Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 0); - assert_ok!(Pools::unbond(Origin::signed(10), 0)); - assert_ok!(Pools::unbond(Origin::signed(40), 0)); + assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond(Origin::signed(40))); let mut current_era = 1; CurrentEra::set(current_era); // In a new era, unbond 550 - assert_ok!(Pools::unbond(Origin::signed(550), 0)); + assert_ok!(Pools::unbond(Origin::signed(550))); // Simulate a slash to the pool with_era(current_era) let mut sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(); @@ -1358,9 +1378,9 @@ mod withdraw_unbonded { // Disable withdraw unbonded so unbond calls do not withdraw funds unbonded // immediately prior. DisableWithdrawUnbonded::set(true); - assert_ok!(Pools::unbond(Origin::signed(10), 0)); - assert_ok!(Pools::unbond(Origin::signed(40), 0)); - assert_ok!(Pools::unbond(Origin::signed(550), 0)); + assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond(Origin::signed(40))); + assert_ok!(Pools::unbond(Origin::signed(550))); DisableWithdrawUnbonded::set(false); SubPoolsStorage::::insert( @@ -1589,7 +1609,7 @@ mod pools_interface { ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given // Unbond in era 0 - assert_ok!(Pools::unbond(Origin::signed(10), 0)); + assert_ok!(Pools::unbond(Origin::signed(10))); // When let SlashPoolOut { slashed_bonded, slashed_unlocking } = @@ -1628,19 +1648,19 @@ mod pools_interface { BondingDuration::set(5); assert_eq!(TotalUnbondingPools::::get(), 7); - assert_ok!(Pools::unbond(Origin::signed(10), 0)); + assert_ok!(Pools::unbond(Origin::signed(10))); CurrentEra::set(1); - assert_ok!(Pools::unbond(Origin::signed(40), 0)); + assert_ok!(Pools::unbond(Origin::signed(40))); CurrentEra::set(3); - assert_ok!(Pools::unbond(Origin::signed(100), 0)); + assert_ok!(Pools::unbond(Origin::signed(100))); CurrentEra::set(5); - assert_ok!(Pools::unbond(Origin::signed(200), 0)); + assert_ok!(Pools::unbond(Origin::signed(200))); CurrentEra::set(6); - assert_ok!(Pools::unbond(Origin::signed(300), 0)); + assert_ok!(Pools::unbond(Origin::signed(300))); // Given assert_eq!( @@ -1699,19 +1719,19 @@ mod pools_interface { BondingDuration::set(5); assert_eq!(TotalUnbondingPools::::get(), 7); - assert_ok!(Pools::unbond(Origin::signed(10), 0)); + assert_ok!(Pools::unbond(Origin::signed(10))); CurrentEra::set(1); - assert_ok!(Pools::unbond(Origin::signed(40), 0)); + assert_ok!(Pools::unbond(Origin::signed(40))); CurrentEra::set(3); - assert_ok!(Pools::unbond(Origin::signed(100), 0)); + assert_ok!(Pools::unbond(Origin::signed(100))); CurrentEra::set(5); - assert_ok!(Pools::unbond(Origin::signed(200), 0)); + assert_ok!(Pools::unbond(Origin::signed(200))); CurrentEra::set(6); - assert_ok!(Pools::unbond(Origin::signed(300), 0)); + assert_ok!(Pools::unbond(Origin::signed(300))); // Given assert_eq!( From 44ee49e9502e02c110716dcd8241d197ec7f4915 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Tue, 8 Feb 2022 14:36:33 -0800 Subject: [PATCH 083/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/nomination-pools/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index d0223c145cb86..1d223a9b65880 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -357,9 +357,9 @@ impl BondedPool { .map(|points| Self { points, account: pool_account.clone() }) } - /// Insert [`Self`] into storage. - fn insert(Self { account, points }: Self) { - BondedPoolPoints::::insert(account, points); + /// Consume and put [`Self`] into storage. + fn put(self) { + BondedPoolPoints::::insert(self.account, self.points); } /// Get the amount of points to issue for some new funds that will be bonded in the pool. From e6fb3f56f388df5364dfba755a2a51c499428dad Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 8 Feb 2022 14:48:47 -0800 Subject: [PATCH 084/299] Add admin roles and make some calls permissionless --- frame/nomination-pools/src/lib.rs | 210 +++++++++++++++++++++++----- frame/nomination-pools/src/tests.rs | 3 +- 2 files changed, 175 insertions(+), 38 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index d0223c145cb86..b90fed1d9c42e 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -340,26 +340,81 @@ pub struct Delegator { unbonding_era: Option, } +/// All of a pool's possible states. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq))] +pub enum PoolState { + Open = 0, + Blocked = 1, + Destroying = 2, +} + +/// Pool permissions and state +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq))] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +struct BondedPoolStorage { + points: BalanceOf, + /// See [`BondedPool::depositor`]. + depositor: T::AccountId, + /// See [`BondedPool::admin`]. + root: T::AccountId, + /// See [`BondedPool::nominator`]. + nominator: T::AccountId, + /// See [`BondedPool::state_toggler`]. + state_toggler: T::AccountId, + /// See [`BondedPool::state_toggler`]. + state: PoolState, +} + #[derive(RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] -// #[codec(mel_bound(T: Config))] -// #[scale_info(skip_type_params(T))] pub struct BondedPool { + /// Points of the pool. points: BalanceOf, + /// Account that puts down a deposit to create the pool. This account acts a delegator, but can + /// only unbond if no other delegators belong to the pool. + depositor: T::AccountId, + /// Can perform the same actions as [`Self::nominator`] and [`Self::state_toggler`]. + /// Additionally, this account can set the `nominator` and `state_toggler` at any time. + root: T::AccountId, + /// Can set the pool's nominations at any time. + nominator: T::AccountId, + /// Can toggle the pools state, including setting the pool as blocked or putting the pool into + /// destruction mode. The state toggle can also "kick" delegators by unbonding them. TODO: + /// there should be an unbond_other where the state_toggle can unbond pool members, and a + /// destroying pool can have anyone unbond other on the members + state_toggler: T::AccountId, + /// State of the pool. + state: PoolState, + /// AccountId of the pool. account: T::AccountId, } impl BondedPool { /// Get [`Self`] from storage. Returns `None` if no entry for `pool_account` exists. fn get(pool_account: &T::AccountId) -> Option { - BondedPoolPoints::::try_get(pool_account) - .ok() - .map(|points| Self { points, account: pool_account.clone() }) + BondedPools::::try_get(pool_account).ok().map( + |BondedPoolStorage { points, depositor, root, nominator, state_toggler, state }| Self { + points, + depositor, + root, + nominator, + state_toggler, + state, + account: pool_account.clone(), + }, + ) } - /// Insert [`Self`] into storage. - fn insert(Self { account, points }: Self) { - BondedPoolPoints::::insert(account, points); + /// Consume and put [`Self`] into storage. + fn put(self) { + let Self { account, points, depositor, root, nominator, state_toggler, state } = self; + BondedPools::::insert( + account, + BondedPoolStorage { points, depositor, root, nominator, state_toggler, state }, + ); } /// Get the amount of points to issue for some new funds that will be bonded in the pool. @@ -412,6 +467,46 @@ impl BondedPool { // `BalanceOf`. Ok(()) } + + fn can_nominate(&self, who: &T::AccountId) -> bool { + *who == self.root || *who == self.nominator + } + + fn ok_to_unbond_other_with( + &self, + who: &T::AccountId, + target_account: &T::AccountId, + target_delegator: &Delegator, + ) -> Result<(), DispatchError> { + let is_destroying = self.state == PoolState::Destroying; + let is_permissioned = who == target_account; + let is_depositor = *target_account == self.depositor; + match (is_permissioned, is_depositor) { + // anyone can unbond a delegator if it is not the depositor and the pool is getting + // destroyed + (false, false) => ensure!(is_destroying, Error::::NotDestroying), + // any delegator who is not the depositor can always unbond themselves + (true, false) => (), + // depositor can only start unbonding if the pool is already being destroyed and the are + // the delegator in the pool + (false, true) | (true, true) => { + ensure!(target_delegator.points == self.points, Error::::NotOnlyDelegator); + ensure!(is_destroying, Error::::NotDestroying); + }, + } + Ok(()) + } + + fn ok_to_withdraw_unbonded_other_with( + &self, + caller: &T::AccountId, + target: &T::AccountId, + ) -> Result<(), DispatchError> { + let is_permissioned = caller == target; + let is_destroying = self.state == PoolState::Destroying; + ensure!(is_permissioned || is_destroying, Error::::NotDestroying); + Ok(()) + } } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] @@ -590,11 +685,10 @@ pub mod pallet { pub(crate) type Delegators = CountedStorageMap<_, Twox64Concat, T::AccountId, Delegator>; - /// Points for the bonded pools. Keyed by the pool's _Stash_/_Controller_. To get or insert a - /// pool see [`BondedPool::get`] and [`BondedPool::insert`] + /// To get or insert a pool see [`BondedPool::get`] and [`BondedPool::put`] #[pallet::storage] - pub(crate) type BondedPoolPoints = - CountedStorageMap<_, Twox64Concat, T::AccountId, BalanceOf>; + pub(crate) type BondedPools = + CountedStorageMap<_, Twox64Concat, T::AccountId, BondedPoolStorage>; /// Reward pools. This is where there rewards for each pool accumulate. When a delegators payout /// is claimed, the balance comes out fo the reward pool. Keyed by the bonded pools @@ -649,6 +743,13 @@ pub mod pallet { // Likely only an error ever encountered in poorly built tests. /// A pool with the generated account id already exists. IdInUse, + /// A pool must be in [`PoolState::Destroying`] in order for the depositor to unbond or for + /// other delegators to be permissionlessly unbonded. + NotDestroying, + /// The depositor must be the only delegator in the pool in order to unbond. + NotOnlyDelegator, + /// The caller does not have nominating permissions for the pool. + NotNominator } #[pallet::call] @@ -707,8 +808,7 @@ pub mod pallet { unbonding_era: None, }, ); - BondedPool::insert(bonded_pool); - + bonded_pool.put(); Self::deposit_event(Event::::Joined { delegator: who, pool: pool_account, @@ -740,22 +840,24 @@ pub mod pallet { /// If their are too many unlocking chunks to unbond with the pool account, /// [`Self::withdraw_unbonded_pool`] can be called to try and minimize unlocking chunks. #[pallet::weight(666)] - pub fn unbond(origin: OriginFor) -> DispatchResult { + pub fn unbond_other(origin: OriginFor, target: T::AccountId) -> DispatchResult { let who = ensure_signed(origin)?; // TODO check if this is owner, if its the owner then they must be the only person in // the pool and it needs to be destroyed with withdraw_unbonded - let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let delegator = Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; let mut bonded_pool = BondedPool::::get(&delegator.pool) .defensive_ok_or_else(|| Error::::PoolNotFound)?; + bonded_pool.ok_to_unbond_other_with(&who, &target, &delegator)?; // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the bonded pool and thus they can no longer claim their payouts. // It is not strictly necessary to claim the rewards, but we do it here for UX. - Self::do_reward_payout(who.clone(), delegator, &bonded_pool)?; + Self::do_reward_payout(target.clone(), delegator, &bonded_pool)?; // Re-fetch the delegator because they where updated by `do_reward_payout`. - let mut delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let mut delegator = + Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPoolsStorage::::get(&delegator.pool).unwrap_or_default(); let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); @@ -782,16 +884,16 @@ pub mod pallet { delegator.unbonding_era = Some(current_era); Self::deposit_event(Event::::Unbonded { - delegator: who.clone(), + delegator: target.clone(), pool: delegator.pool.clone(), amount: balance_to_unbond, }); - // Now that we know everything has worked write the items to storage. - BondedPool::insert(bonded_pool); + bonded_pool.put(); SubPoolsStorage::insert(&delegator.pool, sub_pools); Delegators::insert(who, delegator); + Ok(()) } @@ -811,14 +913,21 @@ pub mod pallet { Ok(()) } + /// Withdraw unbonded funds for the `target` delegator. If the pool is not in + /// [`PoolState::Destroying`] mode, this can only be called by the delegator themselves; + /// otherwise this can be called by any account. #[pallet::weight(666)] - pub fn withdraw_unbonded(origin: OriginFor, num_slashing_spans: u32) -> DispatchResult { + pub fn withdraw_unbonded_other( + origin: OriginFor, + target: T::AccountId, + num_slashing_spans: u32, + ) -> DispatchResult { let who = ensure_signed(origin)?; // TODO: Check if this is the owner, and if its the owner then we delete this pool from // storage at the end of the function. (And we assume that unbond ensured they are the // last member of the pool) - let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let delegator = Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); @@ -826,6 +935,9 @@ pub mod pallet { if current_era.saturating_sub(unbonding_era) < T::StakingInterface::bonding_duration() { return Err(Error::::NotUnbondedYet.into()) }; + BondedPool::::get(&delegator.pool) + .defensive_ok_or_else(|| Error::::RewardPoolNotFound)? + .ok_to_withdraw_unbonded_other_with(&who, &target)?; let mut sub_pools = SubPoolsStorage::::get(&delegator.pool).ok_or(Error::::SubPoolsNotFound)?; @@ -847,20 +959,18 @@ pub mod pallet { }; T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; - T::Currency::transfer( &delegator.pool, - &who, + &target, balance_to_unbond, ExistenceRequirement::AllowDeath, ) .defensive_map_err(|e| e)?; SubPoolsStorage::::insert(&delegator.pool, sub_pools); - Delegators::::remove(&who); - + Delegators::::remove(&target); Self::deposit_event(Event::::Withdrawn { - delegator: who, + delegator: target, pool: delegator.pool, amount: balance_to_unbond, }); @@ -872,8 +982,8 @@ pub mod pallet { /// /// Note that the pool creator will delegate `amount` to the pool and cannot unbond until /// every + /// NOTE: This does not nominate, a pool admin needs to call [`Call::nominate`] /// - /// * `validators`: _Stash_ addresses of the validators to nominate. /// * `amount`: Balance to delegate to the pool. Must meet the minimum bond. /// * `index`: Disambiguation index for seeding account generation. Likely only useful when /// creating multiple pools in the same extrinsic. @@ -881,18 +991,31 @@ pub mod pallet { #[frame_support::transactional] pub fn create( origin: OriginFor, - validators: Vec<::Source>, amount: BalanceOf, index: u16, + root: T::AccountId, + nominator: T::AccountId, + state_toggler: T::AccountId, ) -> DispatchResult { let who = ensure_signed(origin)?; + // TODO: this should be min some deposit + // TODO: have an integrity test that min deposit is at least min bond (make min deposit + // storage item so it can be increased) ensure!(amount >= T::StakingInterface::minimum_bond(), Error::::MinimumBondNotMet); + ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); let (pool_account, reward_account) = Self::create_accounts(index); - ensure!(!BondedPoolPoints::::contains_key(&pool_account), Error::::IdInUse); - - let mut bonded_pool = - BondedPool:: { points: Zero::zero(), account: pool_account.clone() }; + ensure!(!BondedPools::::contains_key(&pool_account), Error::::IdInUse); + + let mut bonded_pool = BondedPool:: { + account: pool_account.clone(), + points: Zero::zero(), + depositor: who.clone(), + root, + nominator, + state_toggler, + state: PoolState::Open, + }; // We must calculate the points issued *before* we bond who's funds, else // points:balance ratio will be wrong. let points_issued = bonded_pool.issue(amount); @@ -904,7 +1027,6 @@ pub mod pallet { amount, reward_account.clone(), )?; - T::StakingInterface::nominate(pool_account.clone(), validators)?; Delegators::::insert( who, @@ -915,7 +1037,7 @@ pub mod pallet { unbonding_era: None, }, ); - BondedPool::::insert(bonded_pool); + bonded_pool.put(); RewardPools::::insert( pool_account, RewardPool:: { @@ -928,6 +1050,20 @@ pub mod pallet { Ok(()) } + + #[pallet::weight(666)] + pub fn nominate( + origin: OriginFor, + pool_account: T::AccountId, + validators: Vec<::Source>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let bonded_pool = + BondedPool::::get(&pool_account).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); + T::StakingInterface::nominate(pool_account.clone(), validators)?; + Ok(()) + } } #[pallet::hooks] @@ -1068,7 +1204,7 @@ impl Pallet { }: SlashPoolArgs::>, ) -> Option>> { // Make sure this is a pool account - BondedPoolPoints::::contains_key(&pool_stash).then(|| ())?; + BondedPools::::contains_key(&pool_stash).then(|| ())?; let mut sub_pools = SubPoolsStorage::::get(pool_stash).unwrap_or_default(); let affected_range = (slash_era + 1)..=apply_era; diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index eb16a3183ce92..53357291bf005 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1270,7 +1270,7 @@ mod pool_withdraw_unbonded { // Then there unbonding balance becomes free assert_eq!(Balances::free_balance(PRIMARY_ACCOUNT), 15); - }) + }); } } @@ -1779,4 +1779,5 @@ mod pools_interface { assert_eq!(slashed_bonded, 0); }); } + // TODO: returns none when account is not a pool } From 4e1280fa8263a362d720644b1b6164cce6ebb98e Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 8 Feb 2022 17:26:43 -0800 Subject: [PATCH 085/299] Destroy pool in withdraw unbonded --- frame/nomination-pools/src/lib.rs | 180 ++++++++++++++++++++++-------- 1 file changed, 131 insertions(+), 49 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index b90fed1d9c42e..1b57b087da753 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -382,9 +382,7 @@ pub struct BondedPool { /// Can set the pool's nominations at any time. nominator: T::AccountId, /// Can toggle the pools state, including setting the pool as blocked or putting the pool into - /// destruction mode. The state toggle can also "kick" delegators by unbonding them. TODO: - /// there should be an unbond_other where the state_toggle can unbond pool members, and a - /// destroying pool can have anyone unbond other on the members + /// destruction mode. The state toggle can also "kick" delegators by unbonding them. state_toggler: T::AccountId, /// State of the pool. state: PoolState, @@ -417,6 +415,11 @@ impl BondedPool { ); } + /// Consume and remove [`Self`] from storage. + fn remove(self) { + BondedPools::::remove(self.account); + } + /// Get the amount of points to issue for some new funds that will be bonded in the pool. fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { let bonded_balance = @@ -472,40 +475,85 @@ impl BondedPool { *who == self.root || *who == self.nominator } + fn can_kick(&self, who: &T::AccountId) -> bool { + (*who == self.root || *who == self.state_toggler) && self.state == PoolState::Blocked + } + + fn is_destroying(&self) -> bool { + self.state == PoolState::Destroying + } + fn ok_to_unbond_other_with( &self, - who: &T::AccountId, + caller: &T::AccountId, target_account: &T::AccountId, target_delegator: &Delegator, ) -> Result<(), DispatchError> { - let is_destroying = self.state == PoolState::Destroying; - let is_permissioned = who == target_account; + let is_permissioned = caller == target_account; let is_depositor = *target_account == self.depositor; match (is_permissioned, is_depositor) { - // anyone can unbond a delegator if it is not the depositor and the pool is getting - // destroyed - (false, false) => ensure!(is_destroying, Error::::NotDestroying), - // any delegator who is not the depositor can always unbond themselves + // If the pool is blocked, then an admin with kicking permissions can remove a + // delegator. If the pool is being destroyed, anyone can remove a delegator + (false, false) => { + ensure!( + self.can_kick(caller) || self.is_destroying(), + Error::::NotKickerOrDestroying + ) + }, + // Any delegator who is not the depositor can always unbond themselves (true, false) => (), - // depositor can only start unbonding if the pool is already being destroyed and the are - // the delegator in the pool + // The depositor can only start unbonding if the pool is already being destroyed and + // they are the delegator in the pool (false, true) | (true, true) => { ensure!(target_delegator.points == self.points, Error::::NotOnlyDelegator); - ensure!(is_destroying, Error::::NotDestroying); + ensure!(self.is_destroying(), Error::::NotDestroying); }, } Ok(()) } + /// Returns a result indicating if `Call::withdraw_unbonded_other`. If the result is ok, the + /// returned value is a bool indicating wether or not to remove the pool from storage. fn ok_to_withdraw_unbonded_other_with( &self, caller: &T::AccountId, - target: &T::AccountId, - ) -> Result<(), DispatchError> { - let is_permissioned = caller == target; - let is_destroying = self.state == PoolState::Destroying; - ensure!(is_permissioned || is_destroying, Error::::NotDestroying); - Ok(()) + target_account: &T::AccountId, + target_delegator: &Delegator, + sub_pools: &SubPools, + ) -> Result { + if *target_account == self.depositor { + // This is a depositor + if !sub_pools.no_era.points.is_zero() { + // Unbonded pool has some points, so if they are the last delegator they must be + // here + ensure!(sub_pools.with_era.len().is_zero(), Error::::NotOnlyDelegator); + ensure!( + sub_pools.no_era.points == target_delegator.points, + Error::::NotOnlyDelegator + ); + } else { + // No points in unbonded pool, so they must be in an unbonding pool + ensure!(sub_pools.with_era.len() == 1, Error::::NotOnlyDelegator); + let only_unbonding_pool = sub_pools + .with_era + .values() + .next() + .expect("ensured at least 1 entry exists on line above. qed."); + ensure!( + only_unbonding_pool.points == target_delegator.points, + Error::::NotOnlyDelegator + ); + } + Ok(true) + } else { + // This isn't a depositor + let is_permissioned = caller == target_account; + ensure!( + is_permissioned || self.can_kick(caller) || self.is_destroying(), + Error::::NotKickerOrDestroying + ); + Ok(false) + } } } @@ -746,10 +794,13 @@ pub mod pallet { /// A pool must be in [`PoolState::Destroying`] in order for the depositor to unbond or for /// other delegators to be permissionlessly unbonded. NotDestroying, - /// The depositor must be the only delegator in the pool in order to unbond. + /// The depositor must be the only delegator in the bonded pool in order to unbond. And the + /// depositor must be the only delegator in the sub pools in order to withdraw unbonded. NotOnlyDelegator, /// The caller does not have nominating permissions for the pool. - NotNominator + NotNominator, + /// Either a) the caller cannot make a valid kick or b) the pool is not destroying + NotKickerOrDestroying, } #[pallet::call] @@ -835,20 +886,32 @@ pub mod pallet { Ok(()) } - /// A bonded delegator can use this to unbond _all_ funds from the pool. + /// Unbond _all_ of the `target` delegators funds from the pool. Under certain conditions, + /// this call can be dispatched permissionlessly (i.e. by any account). + /// + /// Conditions for a permissionless dispatch: + /// + /// - The pool is blocked and the caller is either the root or state-toggler. This is + /// refereed to as a kick. + /// - The pool is destroying and the delegator is not the depositor. + /// - The pool is destroying, the delegator is the depositor and no other delegators are in + /// the pool. + /// + /// Conditions for permissioned dispatch (i.e. the caller is also the target): /// - /// If their are too many unlocking chunks to unbond with the pool account, + /// - The caller is not the depositor + /// - The caller is the depositor, the pool is destroying and not other delegators are in + /// the pool. + /// + /// Note: If their are too many unlocking chunks to unbond with the pool account, /// [`Self::withdraw_unbonded_pool`] can be called to try and minimize unlocking chunks. #[pallet::weight(666)] pub fn unbond_other(origin: OriginFor, target: T::AccountId) -> DispatchResult { - let who = ensure_signed(origin)?; - // TODO check if this is owner, if its the owner then they must be the only person in - // the pool and it needs to be destroyed with withdraw_unbonded - + let caller = ensure_signed(origin)?; let delegator = Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; let mut bonded_pool = BondedPool::::get(&delegator.pool) .defensive_ok_or_else(|| Error::::PoolNotFound)?; - bonded_pool.ok_to_unbond_other_with(&who, &target, &delegator)?; + bonded_pool.ok_to_unbond_other_with(&caller, &target, &delegator)?; // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the bonded pool and thus they can no longer claim their payouts. @@ -891,14 +954,12 @@ pub mod pallet { // Now that we know everything has worked write the items to storage. bonded_pool.put(); SubPoolsStorage::insert(&delegator.pool, sub_pools); - Delegators::insert(who, delegator); - + Delegators::insert(target, delegator); Ok(()) } - /// A permissionless function that allows users to call `withdraw_unbonded` for the pools - /// account. + /// Call `withdraw_unbonded` for the pools account. This call can be made by any account. /// /// This is useful if their are too many unlocking chunks to unbond, and some can be cleared /// by withdrawing. @@ -913,34 +974,43 @@ pub mod pallet { Ok(()) } - /// Withdraw unbonded funds for the `target` delegator. If the pool is not in - /// [`PoolState::Destroying`] mode, this can only be called by the delegator themselves; - /// otherwise this can be called by any account. + /// Withdraw unbonded funds for the `target` delegator. Under certain conditions, + /// this call can be dispatched permissionlessly (i.e. by any account). + /// + /// Conditions for a permissionless dispatch: + /// + /// - The pool is in destroy mode and the target is not the depositor. + /// - The target is the depositor and they are the only delegator in the sub pools. + /// - The pool is blocked and the caller is either the root or state-toggler. + /// + /// Conditions for permissioned dispatch: + /// + /// - The caller is the target and they are not the depositor. + /// + /// Note: If the target is the depositor, the pool will be destroyed. #[pallet::weight(666)] pub fn withdraw_unbonded_other( origin: OriginFor, target: T::AccountId, num_slashing_spans: u32, ) -> DispatchResult { - let who = ensure_signed(origin)?; - // TODO: Check if this is the owner, and if its the owner then we delete this pool from - // storage at the end of the function. (And we assume that unbond ensured they are the - // last member of the pool) - + let caller = ensure_signed(origin)?; let delegator = Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; - let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); - // TODO: make this an ensure - if current_era.saturating_sub(unbonding_era) < T::StakingInterface::bonding_duration() { - return Err(Error::::NotUnbondedYet.into()) - }; - BondedPool::::get(&delegator.pool) - .defensive_ok_or_else(|| Error::::RewardPoolNotFound)? - .ok_to_withdraw_unbonded_other_with(&who, &target)?; + ensure!( + current_era.saturating_sub(unbonding_era) >= + T::StakingInterface::bonding_duration(), + Error::::NotUnbondedYet + ); let mut sub_pools = SubPoolsStorage::::get(&delegator.pool).ok_or(Error::::SubPoolsNotFound)?; + let bonded_pool = BondedPool::::get(&delegator.pool) + .defensive_ok_or_else(|| Error::::PoolNotFound)?; + let should_remove_pool = bonded_pool + .ok_to_withdraw_unbonded_other_with(&caller, &target, &delegator, &sub_pools)?; + let balance_to_unbond = if let Some(pool) = sub_pools.with_era.get_mut(&unbonding_era) { let balance_to_unbond = pool.balance_to_unbond(delegator.points); pool.points = pool.points.saturating_sub(delegator.points); @@ -967,7 +1037,19 @@ pub mod pallet { ) .defensive_map_err(|e| e)?; - SubPoolsStorage::::insert(&delegator.pool, sub_pools); + if should_remove_pool { + let reward_pool = RewardPools::::take(&delegator.pool) + .defensive_ok_or_else(|| Error::::PoolNotFound)?; + SubPoolsStorage::::remove(&delegator.pool); + // Kill accounts from storage by making their balance go below ED. We assume that + // the accounts have no references that would prevent destruction once we get to + // this point. + T::Currency::make_free_balance_be(&reward_pool.account, Zero::zero()); + T::Currency::make_free_balance_be(&bonded_pool.account, Zero::zero()); + bonded_pool.remove(); + } else { + SubPoolsStorage::::insert(&delegator.pool, sub_pools); + } Delegators::::remove(&target); Self::deposit_event(Event::::Withdrawn { delegator: target, From 6388075cddf33c9e50edfcf95de4b4aab7f4f62a Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 8 Feb 2022 17:45:57 -0800 Subject: [PATCH 086/299] Add docs on pool admin --- frame/nomination-pools/src/lib.rs | 37 +++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 1b57b087da753..71c775a73b5cc 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -27,6 +27,7 @@ // * Maintain integrity of slashing events, sufficiently penalizing delegators that where in the // pool while it was backing a validator that got slashed. // * Maximize scalability in terms of delegator count. +//! //! ### Bonded pool //! //! A bonded pool nominates with its total balance, excluding that which has been withdrawn for @@ -174,10 +175,10 @@ //! corresponding unbonding pool. If it's `unbonding_era` is older than `current_era - //! TotalUnbondingPools`, it can cash it's points from the unbonded pool. //! -//! **Relevant extrinsics +//! **Relevant extrinsics:** //! -//! * [`Call::unbond`] -//! * [`Call::withdraw_unbonded`] +//! * [`Call::unbond_other`] +//! * [`Call::withdraw_unbonded_other`] //! //! ### Slashing //! @@ -204,6 +205,34 @@ // // To be fair to joiners, this implementation also need joining pools, which are actively staking, // in addition to the unbonding pools. For maintenance simplicity these are not implemented. +//! +//! ### Pool administration +//! +//! To help facilitate pool adminstration the pool has one of three states (see [`PoolState`]): +//! +//! * Open: Anyone can join the pool and no delegators can be permissionlessly removed. +//! * Blocked: No delegators can join and some admin roles can kick delegators. +//! * Destroying: No delegators can join and all delegators can be permissionlessly removed. +//! +//! A pool has 3 administrative positions (see [`BondedPool`]): +//! +//! * Depositor: creates the pool and is the initial delegator. The can only leave pool once all +//! other delegators have left. Once they fully leave the pool is destroyed. +//! * Nominator: can select which validators the pool nominates. +//! * State-Toggler: can change the pools state and kick delegators if the pool is blocked. +//! * Root: can change the nominator, state-toggler, or itself and can perform any of the actions +//! the nominator or state-toggler can. +//! +//! Note: if it is desired that any of the admin roles are not accessible, they can be set to an +//! anonymous proxy account that has no proxies (and is thus provably keyless). +//! +//! **Relevant extrinsics:** +//! +//! * [`Call::create`] +//! * [`Call::nominate`] +//! * [`Call::unbond_other`] +//! * [`Call::withdraw_unbonded_other`] +//! //! ### Limitations //! //! * Delegators cannot vote with their staked funds because they are transferred into the pools @@ -216,7 +245,7 @@ //! //! * watch out for overflow of [`RewardPoints`] and [`BalanceOf`] types. Consider things like the //! chains total issuance, staking reward rate, and burn rate. -//! # Pool creation and upkeep + // // TBD - possible options: // * Pools can be created by anyone but nominations can never be updated From 3e5202688d2aaf8719d1f86625be9671f8dab08e Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 9 Feb 2022 16:47:05 -0800 Subject: [PATCH 087/299] Fixup tests --- frame/nomination-pools/src/lib.rs | 100 +++---- frame/nomination-pools/src/mock.rs | 17 +- frame/nomination-pools/src/tests.rs | 407 ++++++++++++++++------------ 3 files changed, 301 insertions(+), 223 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 71c775a73b5cc..17989fa80e3df 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -42,13 +42,13 @@ //! equal the value of a point after the transfer. So, when a delegator joins a bonded pool with a //! given `amount_transferred`, we maintain the ratio of bonded balance to points such that: //! -//! ``` +//! ```text //! balance_after_transfer / points_after_transfer == balance_before_transfer / points_before_transfer; //! ``` //! //! To achieve this, we issue points based on the following: //! -//! ``` +//! ```text //! points_issued = (points_before_transfer / balance_before_transfer) * amount_transferred; //! ``` //! @@ -84,7 +84,7 @@ //! 1) Compute the reward pool's total points and the delegator's virtual points in the reward pool //! * First `current_total_earnings` is computed (`current_balance` is the free balance of the //! reward pool at the beginning of these operations.) -//! ``` +//! ```text //! current_total_earnings = //! current_balance - reward_pool.balance + pool.total_earnings; //! ``` @@ -94,29 +94,29 @@ //! earned by the reward pool, the reward pool points are inflated by `bonded_pool.points`. In //! effect this allows each, single unit of balance (e.g. planck) to be divvied up pro-rata //! among delegators based on points. -//! ``` +//! ```text //! new_earnings = current_total_earnings - reward_pool.total_earnings; //! current_points = reward_pool.points + bonding_pool.points * new_earnings; //! ``` //! * Finally, the`delegator_virtual_points` are computed: the product of the delegator's points //! in the bonding pool and the total inflow of balance units since the last time the //! delegator claimed rewards -//! ``` +//! ```text //! new_earnings_since_last_claim = current_total_earnings - delegator.reward_pool_total_earnings; //! delegator_virtual_points = delegator.points * new_earnings_since_last_claim; //! ``` //! 2) Compute the `delegator_payout`: -//! ``` +//! ```text //! delegator_pool_point_ratio = delegator_virtual_points / current_points; //! delegator_payout = current_balance * delegator_pool_point_ratio; //! ``` //! 3) Transfer `delegator_payout` to the delegator //! 4) For the delegator set: -//! ``` +//! ```text //! delegator.reward_pool_total_earnings = current_total_earnings; //! ``` //! 5) For the pool set: -//! ``` +//! ```text //! reward_pool.points = current_points - delegator_virtual_points; //! reward_pool.balance = current_balance - delegator_payout; //! reward_pool.total_earnings = current_total_earnings; @@ -145,7 +145,7 @@ //! When a delegator initiates unbonding it's claim on the bonded pool //! (`balance_to_unbond`) is computed as: //! -//! ``` +//! ```text //! balance_to_unbond = (bonded_pool.balance / bonded_pool.points) * delegator.points; //! ``` //! @@ -155,7 +155,7 @@ //! points to balance ratio properties as the bonded pool, so delegator points in the //! unbonding pool are issued based on //! -//! ``` +//! ```text //! new_points_issued = (points_before_transfer / balance_before_transfer) * balance_to_unbond; //! ``` //! @@ -163,7 +163,7 @@ //! [`TotalUnbondingPools`]). An unbonding pool is removed once its older than `current_era - //! TotalUnbondingPools`. An unbonding pool is merged into the unbonded pool with //! -//! ``` +//! ```text //! unbounded_pool.balance = unbounded_pool.balance + unbonding_pool.balance; //! unbounded_pool.points = unbounded_pool.points + unbonding_pool.points; //! ``` @@ -245,36 +245,20 @@ //! //! * watch out for overflow of [`RewardPoints`] and [`BalanceOf`] types. Consider things like the //! chains total issuance, staking reward rate, and burn rate. - // -// TBD - possible options: -// * Pools can be created by anyone but nominations can never be updated -// * Pools can be created by anyone and the creator can update the validators -// * Pools are created by governance and governance can update the validators -// ... Other ideas -// * pools can have different roles assigned: creator (puts deposit down, cannot remove deposit -// until pool is empty), admin (can control who is nominator, destroyer and can do -// nominate/destroy), nominator (can adjust nominators), destroyer (can initiate destroy), etc -// - // Invariants -// - A `delegator.pool` must always be a valid entry in `RewardPools`, and `BondedPoolPoints`. -// - Every entry in `BondedPoolPoints` must have a corresponding entry in `RewardPools` -// - If a delegator unbonds, the sub pools should always correctly track slashses such that the +// * A `delegator.pool` must always be a valid entry in `RewardPools`, and `BondedPoolPoints`. +// * Every entry in `BondedPoolPoints` must have a corresponding entry in `RewardPools` +// * If a delegator unbonds, the sub pools should always correctly track slashses such that the // calculated amount when withdrawing unbonded is a lower bound of the pools free balance. +// * If the depositor is actively unbonding, the pool is in destroying state #![cfg_attr(not(feature = "std"), no_std)] // TODO: -// - make withdraw unbonded pool that just calls withdraw unbonded for the underlying pool account - -// and remove withdraw unbonded call from unbond -// - creation -// - CreateOrigin: the type of origin that can create a pool - can be set with governance call -// - creator: account that cannont unbond until there are no other pool members (essentially -// deposit) -// - kicker: can kick (force unbond) delegators and block new delegators -// - nominator: can set nominations -// - admin: can change kicker, nominator, and make another account admin. +// - test permissionless use cases of unbond and withdraw unbonded +// - test depositor unbond and unbond withdraws +// - test nominate // - checks for number of pools when creating pools (param for max pools, pool creation origin) // - post checks that rewardpool::count == bondedpool::count. delegators >= bondedpool::count, // subpools::count <= bondedpools @@ -787,6 +771,7 @@ pub mod pallet { PaidOut { delegator: T::AccountId, pool: T::AccountId, payout: BalanceOf }, Unbonded { delegator: T::AccountId, pool: T::AccountId, amount: BalanceOf }, Withdrawn { delegator: T::AccountId, pool: T::AccountId, amount: BalanceOf }, + DustWithdrawn { delegator: T::AccountId, pool: T::AccountId }, } #[pallet::error] @@ -1033,8 +1018,8 @@ pub mod pallet { Error::::NotUnbondedYet ); - let mut sub_pools = - SubPoolsStorage::::get(&delegator.pool).ok_or(Error::::SubPoolsNotFound)?; + let mut sub_pools = SubPoolsStorage::::get(&delegator.pool) + .defensive_ok_or_else(|| Error::::SubPoolsNotFound)?; let bonded_pool = BondedPool::::get(&delegator.pool) .defensive_ok_or_else(|| Error::::PoolNotFound)?; let should_remove_pool = bonded_pool @@ -1058,13 +1043,28 @@ pub mod pallet { }; T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; - T::Currency::transfer( - &delegator.pool, - &target, - balance_to_unbond, - ExistenceRequirement::AllowDeath, - ) - .defensive_map_err(|e| e)?; + if T::Currency::free_balance(&delegator.pool) >= balance_to_unbond { + T::Currency::transfer( + &delegator.pool, + &target, + balance_to_unbond, + ExistenceRequirement::AllowDeath, + ) + .defensive_map_err(|e| e)?; + Self::deposit_event(Event::::Withdrawn { + delegator: target.clone(), + pool: delegator.pool.clone(), + amount: balance_to_unbond, + }); + } else { + // This should only happen in the case a previous withdraw put the pools balance + // below ED and it was dusted. We gracefully carry primarily to ensure the pool can + // eventually be destroyed + Self::deposit_event(Event::::DustWithdrawn { + delegator: target.clone(), + pool: delegator.pool.clone(), + }); + } if should_remove_pool { let reward_pool = RewardPools::::take(&delegator.pool) @@ -1080,11 +1080,6 @@ pub mod pallet { SubPoolsStorage::::insert(&delegator.pool, sub_pools); } Delegators::::remove(&target); - Self::deposit_event(Event::::Withdrawn { - delegator: target, - pool: delegator.pool, - amount: balance_to_unbond, - }); Ok(()) } @@ -1374,6 +1369,15 @@ impl Pallet { }; Some(SlashPoolOut { slashed_bonded, slashed_unlocking }) } + + #[cfg(test)] + fn set_state(pool_account: &T::AccountId, state: PoolState) -> Result<(), ()> { + BondedPools::::try_mutate(pool_account, |maybe_bonded_pool| { + maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { + bonded_pool.state = state; + }) + }) + } } impl PoolsInterface for Pallet { diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index bbd78d6ad7c13..988ce83f04f72 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -16,7 +16,6 @@ parameter_types! { static BondedBalanceMap: std::collections::HashMap = Default::default(); static UnbondingBalanceMap: std::collections::HashMap = Default::default(); pub static BondingDuration: EraIndex = 3; - pub static DisableWithdrawUnbonded: bool = false; } pub struct StakingMock; @@ -60,13 +59,6 @@ impl sp_staking::StakingInterface for StakingMock { } fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result { - if DisableWithdrawUnbonded::get() { - // We have a naive impl - it will always withdraw whatever is unbonding regardless of - // era So sometimes we may want to disable it to simulate calls in eras where there is - // nothing to completely unlock. - return Ok(1) - } - let maybe_new_free = UNBONDING_BALANCE_MAP.with(|m| m.borrow_mut().remove(&who)); if let Some(new_free) = maybe_new_free { assert_ok!(Balances::mutate_account(&who, |a| a.free += new_free)); @@ -195,7 +187,14 @@ impl ExtBuilder { // make a pool let amount_to_bond = ::StakingInterface::minimum_bond(); Balances::make_free_balance_be(&10, amount_to_bond * 2); - assert_ok!(Pools::create(RawOrigin::Signed(10).into(), vec![100], amount_to_bond, 0)); + assert_ok!(Pools::create( + RawOrigin::Signed(10).into(), + amount_to_bond, + 0, + 900, + 901, + 902 + )); for (account_id, bonded) in self.delegators { Balances::make_free_balance_be(&account_id, bonded * 2); diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 53357291bf005..4e9eb4ffa52f8 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1,8 +1,7 @@ use super::*; use crate::mock::{ - Balance, Balances, BondingDuration, CurrentEra, DisableWithdrawUnbonded, ExistentialDeposit, - ExtBuilder, Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, - UNBONDING_BALANCE_MAP, + Balance, Balances, BondingDuration, CurrentEra, ExistentialDeposit, ExtBuilder, Origin, Pools, + Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, UNBONDING_BALANCE_MAP, }; use frame_support::{assert_noop, assert_ok}; @@ -17,14 +16,22 @@ macro_rules! sub_pools_with_era { #[test] fn test_setup_works() { ExtBuilder::default().build_and_execute(|| { - assert_eq!(BondedPoolPoints::::count(), 1); + assert_eq!(BondedPools::::count(), 1); assert_eq!(RewardPools::::count(), 1); assert_eq!(SubPoolsStorage::::count(), 0); assert_eq!(Delegators::::count(), 1); assert_eq!( BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), - BondedPool:: { points: 10, account: PRIMARY_ACCOUNT } + BondedPool:: { + depositor: 10, + state: PoolState::Open, + points: 10, + account: PRIMARY_ACCOUNT, + root: 900, + nominator: 901, + state_toggler: 902, + } ); assert_eq!( RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), @@ -49,7 +56,7 @@ fn test_setup_works() { #[test] fn exercise_delegator_life_cycle() { - todo!() + // todo!() // create pool // join pool // claim rewards @@ -61,7 +68,15 @@ mod bonded_pool { use super::*; #[test] fn points_to_issue_works() { - let mut bonded_pool = BondedPool:: { points: 100, account: 123 }; + let mut bonded_pool = BondedPool:: { + depositor: 10, + state: PoolState::Open, + points: 100, + account: 123, + root: 900, + nominator: 901, + state_toggler: 902, + }; // 1 points : 1 balance ratio StakingMock::set_bonded_balance(123, 100); @@ -105,7 +120,15 @@ mod bonded_pool { #[test] fn balance_to_unbond_works() { // 1 balance : 1 points ratio - let mut bonded_pool = BondedPool:: { points: 100, account: 123 }; + let mut bonded_pool = BondedPool:: { + depositor: 10, + state: PoolState::Open, + points: 100, + account: 123, + root: 900, + nominator: 901, + state_toggler: 902, + }; StakingMock::set_bonded_balance(123, 100); assert_eq!(bonded_pool.balance_to_unbond(10), 10); assert_eq!(bonded_pool.balance_to_unbond(0), 0); @@ -143,7 +166,15 @@ mod bonded_pool { #[test] fn ok_to_join_with_works() { ExtBuilder::default().build_and_execute(|| { - let pool = BondedPool:: { points: 100, account: 123 }; + let pool = BondedPool:: { + depositor: 10, + state: PoolState::Open, + points: 100, + account: 123, + root: 900, + nominator: 901, + state_toggler: 902, + }; // Simulate a 100% slashed pool StakingMock::set_bonded_balance(123, 0); @@ -170,15 +201,7 @@ mod bonded_pool { } } -mod reward_pool { - use super::*; - - #[test] - fn update_total_earnings_and_balance_works() { - // let reward_pool = RewardPool:: - todo!() - } -} +mod reward_pool {} mod unbond_pool { use super::*; @@ -332,7 +355,15 @@ mod join { #[test] fn join_works() { - let bonded = |points| BondedPool:: { points, account: PRIMARY_ACCOUNT }; + let bonded = |points| BondedPool:: { + depositor: 10, + state: PoolState::Open, + points, + account: PRIMARY_ACCOUNT, + root: 900, + nominator: 901, + state_toggler: 902, + }; ExtBuilder::default().build_and_execute(|| { // Given Balances::make_free_balance_be(&11, ExistentialDeposit::get() + 2); @@ -395,7 +426,16 @@ mod join { Error::::OverflowRisk ); - BondedPool::::insert(BondedPool:: { points: 100, account: 123 }); + BondedPool:: { + depositor: 10, + state: PoolState::Open, + points: 100, + account: 123, + root: 900, + nominator: 901, + state_toggler: 902, + } + .put(); // Force the points:balance ratio to 100/10 (so 10) StakingMock::set_bonded_balance(123, 10); assert_noop!(Pools::join(Origin::signed(11), 420, 123), Error::::OverflowRisk); @@ -415,7 +455,16 @@ mod join { fn join_panics_when_reward_pool_not_found() { ExtBuilder::default().build_and_execute(|| { StakingMock::set_bonded_balance(123, 100); - BondedPool::::insert(BondedPool:: { points: 100, account: 123 }); + BondedPool:: { + depositor: 10, + state: PoolState::Open, + points: 100, + account: 123, + root: 900, + nominator: 901, + state_toggler: 902, + } + .put(); let _ = Pools::join(Origin::signed(11), 420, 123); }); } @@ -1104,7 +1153,8 @@ mod unbond { #[test] fn unbond_pool_of_1_works() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(Pools::unbond(Origin::signed(10))); + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, @@ -1113,7 +1163,15 @@ mod unbond { assert_eq!( BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), - BondedPool { points: 0, account: PRIMARY_ACCOUNT } + BondedPool { + depositor: 10, + state: PoolState::Destroying, + points: 0, + account: PRIMARY_ACCOUNT, + root: 900, + nominator: 901, + state_toggler: 902, + } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); @@ -1131,7 +1189,7 @@ mod unbond { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 600); // When - assert_ok!(Pools::unbond(Origin::signed(40))); + assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); // Then assert_eq!( @@ -1140,30 +1198,47 @@ mod unbond { ); assert_eq!( BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), - BondedPool { points: 560, account: PRIMARY_ACCOUNT } + BondedPool { + depositor: 10, + state: PoolState::Open, + points: 560, + account: PRIMARY_ACCOUNT, + root: 900, + nominator: 901, + state_toggler: 902, + } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 94); assert_eq!(Delegators::::get(40).unwrap().unbonding_era, Some(0)); assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding // When - assert_ok!(Pools::unbond(Origin::signed(10))); + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); // Then assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, - sub_pools_with_era! { 0 => UnbondPool { points: 7, balance: 7 }} + sub_pools_with_era! { 0 => UnbondPool { points: 98, balance: 98 }} ); assert_eq!( BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), - BondedPool { points: 550, account: PRIMARY_ACCOUNT } + BondedPool { + depositor: 10, + state: PoolState::Destroying, + points: 10, + account: PRIMARY_ACCOUNT, + root: 900, + nominator: 901, + state_toggler: 902, + } ); - assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 93); - assert_eq!(Delegators::::get(10).unwrap().unbonding_era, Some(0)); - assert_eq!(Balances::free_balance(&10), 10 + 10); + assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 2); + assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0)); + assert_eq!(Balances::free_balance(&550), 550 + 550); // When - assert_ok!(Pools::unbond(Origin::signed(550))); + assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); // Then assert_eq!( @@ -1172,7 +1247,15 @@ mod unbond { ); assert_eq!( BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), - BondedPool { points: 0, account: PRIMARY_ACCOUNT } + BondedPool { + depositor: 10, + state: PoolState::Destroying, + points: 0, + account: PRIMARY_ACCOUNT, + root: 900, + nominator: 901, + state_toggler: 902, + } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0)); @@ -1195,12 +1278,13 @@ mod unbond { }, }, ); + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); // When let current_era = 1 + TotalUnbondingPools::::get(); CurrentEra::set(current_era); - assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); // Then assert_eq!( @@ -1220,7 +1304,10 @@ mod unbond { #[should_panic = "Defensive failure has been triggered!"] fn unbond_errors_correctly() { ExtBuilder::default().build_and_execute(|| { - assert_noop!(Pools::unbond(Origin::signed(11)), Error::::DelegatorNotFound); + assert_noop!( + Pools::unbond_other(Origin::signed(11), 11), + Error::::DelegatorNotFound + ); // Add the delegator let delegator = Delegator { @@ -1231,7 +1318,7 @@ mod unbond { }; Delegators::::insert(11, delegator); - let _ = Pools::unbond(Origin::signed(11)); + let _ = Pools::unbond_other(Origin::signed(11), 11); }); } @@ -1246,10 +1333,18 @@ mod unbond { unbonding_era: None, }; Delegators::::insert(11, delegator); - let bonded_pool = BondedPool { points: 10, account: 1 }; - BondedPool::::insert(bonded_pool); + BondedPool:: { + depositor: 10, + state: PoolState::Open, + points: 10, + account: 1, + root: 900, + nominator: 901, + state_toggler: 902, + } + .put(); - let _ = Pools::unbond(Origin::signed(11)); + let _ = Pools::unbond_other(Origin::signed(11), 11); }); } } @@ -1274,40 +1369,37 @@ mod pool_withdraw_unbonded { } } -mod withdraw_unbonded { +mod withdraw_unbonded_other { use super::*; #[test] - fn withdraw_unbonded_works_against_slashed_no_era_sub_pool() { + fn withdraw_unbonded_other_works_against_slashed_no_era_sub_pool() { ExtBuilder::default() .add_delegators(vec![(40, 40), (550, 550)]) .build_and_execute(|| { // Given - Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 0); - assert_ok!(Pools::unbond(Origin::signed(10))); - assert_ok!(Pools::unbond(Origin::signed(40))); + assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); + assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); let mut current_era = 1; CurrentEra::set(current_era); + // In a new era, unbond the depositor + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); - // In a new era, unbond 550 - assert_ok!(Pools::unbond(Origin::signed(550))); - - // Simulate a slash to the pool with_era(current_era) let mut sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(); let unbond_pool = sub_pools.with_era.get_mut(¤t_era).unwrap(); - // Sanity check - assert_eq!(*unbond_pool, UnbondPool { points: 550, balance: 550 }); + assert_eq!(*unbond_pool, UnbondPool { points: 10, balance: 10 }); - // Decrease the balance by half - unbond_pool.balance = 275; + // Simulate a slash to the pool with_era(current_era) decreasing the balance by half + unbond_pool.balance = 5; SubPoolsStorage::::insert(PRIMARY_ACCOUNT, sub_pools); // Update the equivalent of the unbonding chunks for the `StakingMock` UNBONDING_BALANCE_MAP - .with(|m| *m.borrow_mut().get_mut(&PRIMARY_ACCOUNT).unwrap() -= 275); + .with(|m| *m.borrow_mut().get_mut(&PRIMARY_ACCOUNT).unwrap() -= 5); // Advance the current_era to ensure all `with_era` pools will be merged into // `no_era` pool @@ -1319,55 +1411,57 @@ mod withdraw_unbonded { let sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT) .unwrap() .maybe_merge_pools(current_era); + SubPoolsStorage::::insert(PRIMARY_ACCOUNT, sub_pools); assert_eq!( - sub_pools, + SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), SubPools { with_era: Default::default(), - no_era: UnbondPool { balance: 275 + 40 + 10, points: 550 + 40 + 10 } + no_era: UnbondPool { points: 550 + 40 + 10, balance: 550 + 40 + 5 } } ); - SubPoolsStorage::::insert(PRIMARY_ACCOUNT, sub_pools); // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 0)); + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(550), 550, 0)); // Then assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().no_era, - UnbondPool { points: 40 + 10, balance: 275 + 40 + 10 - 297 } + UnbondPool { points: 40 + 10, balance: 40 + 5 + 5 } ); - assert_eq!(Balances::free_balance(&550), 550 + 297); - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 275 + 40 + 10 - 297); + assert_eq!(Balances::free_balance(&550), 550 + 545); + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 50); assert!(!Delegators::::contains_key(550)); // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 0)); + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(40), 40, 0)); // Then assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().no_era, - UnbondPool { points: 10, balance: 28 - 22 } + UnbondPool { points: 10, balance: 10 } ); - assert_eq!(Balances::free_balance(&40), 40 + 22); - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 28 - 22); + assert_eq!(Balances::free_balance(&40), 40 + 40); + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 50 - 40); assert!(!Delegators::::contains_key(40)); // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 0)); + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(10), 10, 0)); // Then - assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().no_era, - UnbondPool { points: 0, balance: 0 } - ); - assert_eq!(Balances::free_balance(&10), 10 + 6); + assert_eq!(Balances::free_balance(&10), 10 + 10); assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 0); assert!(!Delegators::::contains_key(10)); + // Pools are removed from storage because the depositor left + assert!(!SubPoolsStorage::::contains_key(&PRIMARY_ACCOUNT),); + assert!(!RewardPools::::contains_key(&PRIMARY_ACCOUNT),); + assert!(!BondedPools::::contains_key(&PRIMARY_ACCOUNT),); }); } + // This test also documents the case when the pools free balance goes below ED before all + // delegators have unbonded. #[test] - fn withdraw_unbonded_works_against_slashed_with_era_sub_pools() { + fn withdraw_unbonded_other_works_against_slashed_with_era_sub_pools() { ExtBuilder::default() .add_delegators(vec![(40, 40), (550, 550)]) .build_and_execute(|| { @@ -1375,13 +1469,10 @@ mod withdraw_unbonded { StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); // slash bonded balance Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 0); - // Disable withdraw unbonded so unbond calls do not withdraw funds unbonded - // immediately prior. - DisableWithdrawUnbonded::set(true); - assert_ok!(Pools::unbond(Origin::signed(10))); - assert_ok!(Pools::unbond(Origin::signed(40))); - assert_ok!(Pools::unbond(Origin::signed(550))); - DisableWithdrawUnbonded::set(false); + assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); + assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); SubPoolsStorage::::insert( PRIMARY_ACCOUNT, @@ -1393,7 +1484,7 @@ mod withdraw_unbonded { CurrentEra::set(StakingMock::bonding_duration()); // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 0)); + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(40), 40, 0)); // Then assert_eq!( @@ -1405,41 +1496,49 @@ mod withdraw_unbonded { assert!(!Delegators::::contains_key(40)); // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 0)); + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(550), 550, 0)); // Then assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, - sub_pools_with_era! { 0 => UnbondPool { points: 550, balance: 93 }} + sub_pools_with_era! { 0 => UnbondPool { points: 10, balance: 2 }} ); - assert_eq!(Balances::free_balance(&10), 10 + 1); - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 93); - assert!(!Delegators::::contains_key(10)); + assert_eq!(Balances::free_balance(&550), 550 + 92); + // The account was dusted because it went below ED(5) + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 0); + assert!(!Delegators::::contains_key(550)); // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 0)); + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(10), 10, 0)); // Then - assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, - sub_pools_with_era! { 0 => UnbondPool { points: 0, balance: 0 }} - ); - assert_eq!(Balances::free_balance(&550), 550 + 93); + assert_eq!(Balances::free_balance(&10), 10 + 0); assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 0); - assert!(!Delegators::::contains_key(550)); + assert!(!Delegators::::contains_key(10)); + // Pools are removed from storage because the depositor left + assert!(!SubPoolsStorage::::contains_key(&PRIMARY_ACCOUNT),); + assert!(!RewardPools::::contains_key(&PRIMARY_ACCOUNT),); + assert!(!BondedPools::::contains_key(&PRIMARY_ACCOUNT),); }); } #[test] - fn withdraw_unbonded_errors_correctly() { + fn withdraw_unbonded_other_errors_correctly() { ExtBuilder::default().build_and_execute(|| { + // Insert the sub-pool + let sub_pools = SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { 0 => UnbondPool { points: 10, balance: 10 }}, + }; + SubPoolsStorage::::insert(123, sub_pools.clone()); + assert_noop!( - Pools::withdraw_unbonded(Origin::signed(11), 0), + Pools::withdraw_unbonded_other(Origin::signed(11), 11, 0), Error::::DelegatorNotFound ); let mut delegator = Delegator { - pool: 123, + pool: PRIMARY_ACCOUNT, points: 10, reward_pool_total_earnings: 0, unbonding_era: None, @@ -1448,7 +1547,7 @@ mod withdraw_unbonded { // The delegator has not called `unbond` assert_noop!( - Pools::withdraw_unbonded(Origin::signed(11), 0), + Pools::withdraw_unbonded_other(Origin::signed(11), 11, 0), Error::::NotUnbonding ); @@ -1458,61 +1557,25 @@ mod withdraw_unbonded { // We are still in the bonding duration assert_noop!( - Pools::withdraw_unbonded(Origin::signed(11), 0), + Pools::withdraw_unbonded_other(Origin::signed(11), 11, 0), Error::::NotUnbondedYet ); // Skip ahead to the end of the bonding duration CurrentEra::set(StakingMock::bonding_duration()); - // We encounter a system logic error;` the sub-pools would normally be created in - // `unbond` - assert_noop!( - Pools::withdraw_unbonded(Origin::signed(11), 0), - Error::::SubPoolsNotFound - ); - - // Insert the sub-pool - let sub_pools = SubPools { - no_era: Default::default(), - with_era: sub_pools_with_era! { 0 => UnbondPool { points: 10, balance: 10 }}, - }; - SubPoolsStorage::::insert(123, sub_pools.clone()); + // TODO + // assert_noop!( + // Pools::withdraw_unbonded_other(Origin::signed(10), 10, 0), + // Error::::NotOnlyDelegator + // ); // If we error the delegator does not get removed assert_eq!(Delegators::::get(&11), Some(delegator)); - // and the subpools do not get updated. + // and the sub pools do not get updated. assert_eq!(SubPoolsStorage::::get(123).unwrap(), sub_pools) }); } - - #[test] - #[should_panic = "Defensive failure has been triggered!: Module(ModuleError { index: 1, error: 2, message: Some(\"InsufficientBalance\") })"] - fn withdraw_unbonded_test_panics_if_funds_cannot_be_transferred() { - ExtBuilder::default().build_and_execute(|| { - // Insert a delegator that starts unbonding in era 0 - let delegator = Delegator { - pool: 123, - points: 10, - reward_pool_total_earnings: 0, - unbonding_era: Some(0), - }; - Delegators::::insert(11, delegator.clone()); - - // Skip ahead to the end of the bonding duration - CurrentEra::set(StakingMock::bonding_duration()); - - // Insert the sub-pool - let sub_pools = SubPools { - no_era: Default::default(), - with_era: sub_pools_with_era! { 0 => UnbondPool { points: 10, balance: 10 }}, - }; - SubPoolsStorage::::insert(123, sub_pools.clone()); - - // Panics - let _ = Pools::withdraw_unbonded(Origin::signed(11), 0); - }); - } } mod create { @@ -1523,13 +1586,20 @@ mod create { ExtBuilder::default().build_and_execute(|| { let stash = 337692978; - assert!(!BondedPoolPoints::::contains_key(1)); + assert!(!BondedPools::::contains_key(1)); assert!(!RewardPools::::contains_key(1)); assert!(!Delegators::::contains_key(11)); assert_eq!(StakingMock::bonded_balance(&stash), None); Balances::make_free_balance_be(&11, StakingMock::minimum_bond()); - assert_ok!(Pools::create(Origin::signed(11), vec![], StakingMock::minimum_bond(), 42)); + assert_ok!(Pools::create( + Origin::signed(11), + StakingMock::minimum_bond(), + 42, + 123, + 456, + 789 + )); assert_eq!(Balances::free_balance(&11), 0); assert_eq!( @@ -1543,7 +1613,15 @@ mod create { ); assert_eq!( BondedPool::::get(&stash).unwrap(), - BondedPool { points: StakingMock::minimum_bond(), account: stash.clone() } + BondedPool { + points: StakingMock::minimum_bond(), + depositor: 11, + state: PoolState::Open, + account: stash.clone(), + root: 123, + nominator: 456, + state_toggler: 789 + } ); assert_eq!(StakingMock::bonded_balance(&stash).unwrap(), StakingMock::minimum_bond()); assert_eq!( @@ -1563,12 +1641,12 @@ mod create { fn create_errors_correctly() { ExtBuilder::default().build_and_execute(|| { assert_noop!( - Pools::create(Origin::signed(11), vec![], 420, 0), + Pools::create(Origin::signed(11), 420, 0, 123, 456, 789), Error::::IdInUse ); assert_noop!( - Pools::create(Origin::signed(11), vec![], 1, 42), + Pools::create(Origin::signed(11), 1, 42, 123, 456, 789), Error::::MinimumBondNotMet ); }); @@ -1600,16 +1678,13 @@ mod pools_interface { ); assert_eq!(slashed_unlocking, Default::default()); assert_eq!(slashed_bonded, 1); - - // Slash, some sub pools are in range, some are out - // Same as above, but a slash amount greater than total slashable }); // Slash, but all sub pools are out of range ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given // Unbond in era 0 - assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); // When let SlashPoolOut { slashed_bonded, slashed_unlocking } = @@ -1629,7 +1704,7 @@ mod pools_interface { SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { - 0 => UnbondPool { points: 10, balance: 10 } + 0 => UnbondPool { points: 100, balance: 100 } } } ); @@ -1648,19 +1723,19 @@ mod pools_interface { BondingDuration::set(5); assert_eq!(TotalUnbondingPools::::get(), 7); - assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond_other(Origin::signed(400), 400)); CurrentEra::set(1); - assert_ok!(Pools::unbond(Origin::signed(40))); + assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); CurrentEra::set(3); - assert_ok!(Pools::unbond(Origin::signed(100))); + assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); CurrentEra::set(5); - assert_ok!(Pools::unbond(Origin::signed(200))); + assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); CurrentEra::set(6); - assert_ok!(Pools::unbond(Origin::signed(300))); + assert_ok!(Pools::unbond_other(Origin::signed(300), 300)); // Given assert_eq!( @@ -1668,7 +1743,7 @@ mod pools_interface { SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { - 0 => UnbondPool { points: 10, balance: 10 }, + 0 => UnbondPool { points: 400, balance: 400 }, 1 => UnbondPool { points: 40, balance: 40 }, 3 => UnbondPool { points: 100, balance: 100 }, 5 => UnbondPool { points: 200, balance: 200 }, @@ -1681,10 +1756,10 @@ mod pools_interface { let SlashPoolOut { slashed_bonded, slashed_unlocking } = Pools::slash_pool(SlashPoolArgs { pool_stash: &PRIMARY_ACCOUNT, - slash_amount: (40 + 100 + 200 + 400) / 2, + slash_amount: (40 + 100 + 200 + 10) / 2, slash_era: 0, apply_era: 5, - active_bonded: 400, + active_bonded: 10, }) .unwrap(); @@ -1694,7 +1769,7 @@ mod pools_interface { SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { - 0 => UnbondPool { points: 10, balance: 10 }, + 0 => UnbondPool { points: 400, balance: 400 }, 1 => UnbondPool { points: 40, balance: 40 / 2 }, 3 => UnbondPool { points: 100, balance: 100 / 2}, 5 => UnbondPool { points: 200, balance: 200 / 2}, @@ -1705,7 +1780,7 @@ mod pools_interface { let expected_slashed_unlocking: BTreeMap<_, _> = [(1, 40 / 2), (3, 100 / 2), (5, 200 / 2)].into_iter().collect(); assert_eq!(slashed_unlocking, expected_slashed_unlocking); - assert_eq!(slashed_bonded, 400 / 2); + assert_eq!(slashed_bonded, 10 / 2); }); } @@ -1719,19 +1794,19 @@ mod pools_interface { BondingDuration::set(5); assert_eq!(TotalUnbondingPools::::get(), 7); - assert_ok!(Pools::unbond(Origin::signed(10))); + assert_ok!(Pools::unbond_other(Origin::signed(400), 400)); CurrentEra::set(1); - assert_ok!(Pools::unbond(Origin::signed(40))); + assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); CurrentEra::set(3); - assert_ok!(Pools::unbond(Origin::signed(100))); + assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); CurrentEra::set(5); - assert_ok!(Pools::unbond(Origin::signed(200))); + assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); CurrentEra::set(6); - assert_ok!(Pools::unbond(Origin::signed(300))); + assert_ok!(Pools::unbond_other(Origin::signed(300), 300)); // Given assert_eq!( @@ -1739,7 +1814,7 @@ mod pools_interface { SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { - 0 => UnbondPool { points: 10, balance: 10 }, + 0 => UnbondPool { points: 400, balance: 400 }, 1 => UnbondPool { points: 40, balance: 40 }, 3 => UnbondPool { points: 100, balance: 100 }, 5 => UnbondPool { points: 200, balance: 200 }, @@ -1755,7 +1830,7 @@ mod pools_interface { slash_amount: 40 + 100 + 200 + 400 + 10, slash_era: 0, apply_era: 5, - active_bonded: 400, + active_bonded: 10, }) .unwrap(); @@ -1765,7 +1840,7 @@ mod pools_interface { SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { - 0 => UnbondPool { points: 10, balance: 10 }, + 0 => UnbondPool { points: 400, balance: 400 }, 1 => UnbondPool { points: 40, balance: 0 }, 3 => UnbondPool { points: 100, balance: 0 }, 5 => UnbondPool { points: 200, balance: 0 }, From 1605718f97634bd938df705566f050ab78c131a6 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 9 Feb 2022 19:56:25 -0800 Subject: [PATCH 088/299] Test unbond_other permissionless scenarios --- frame/nomination-pools/src/tests.rs | 116 +++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 4e9eb4ffa52f8..4d2261d494353 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1151,7 +1151,7 @@ mod unbond { use super::*; #[test] - fn unbond_pool_of_1_works() { + fn unbond_other_of_1_works() { ExtBuilder::default().build_and_execute(|| { Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); @@ -1179,7 +1179,7 @@ mod unbond { } #[test] - fn unbond_pool_of_3_works() { + fn unbond_other_of_3_works() { ExtBuilder::default() .add_delegators(vec![(40, 40), (550, 550)]) .build_and_execute(|| { @@ -1300,6 +1300,118 @@ mod unbond { }); } + #[test] + fn unbond_other_kick_works() { + // Kick: the pool is blocked and the caller is either the root or state-toggler. + ExtBuilder::default() + .add_delegators(vec![(100, 100), (200, 200)]) + .build_and_execute(|| { + // Given + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + assert_eq!(bonded_pool.root, 900); + assert_eq!(bonded_pool.nominator, 901); + assert_eq!(bonded_pool.state_toggler, 902); + + // When the nominator trys to kick, then its a noop + assert_noop!( + Pools::unbond_other(Origin::signed(901), 100), + Error::::NotKickerOrDestroying + ); + + // When the root kicks then its ok + assert_ok!(Pools::unbond_other(Origin::signed(900), 100)); + + // When the state toggler kicks then its ok + assert_ok!(Pools::unbond_other(Origin::signed(902), 200)); + + assert_eq!( + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool { + root: 900, + nominator: 901, + state_toggler: 902, + account: PRIMARY_ACCOUNT, + depositor: 10, + state: PoolState::Blocked, + points: 10 // Only 10 points because 200 + 100 was unbonded + } + ); + assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 10); + assert_eq!( + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { + 0 => UnbondPool { points: 100 + 200, balance: 100 + 200 } + }, + } + ); + assert_eq!( + UNBONDING_BALANCE_MAP.with(|m| *m.borrow_mut().get(&PRIMARY_ACCOUNT).unwrap()), + 100 + 200 + ); + }); + } + + #[test] + fn unbond_other_with_non_admins_works() { + // Scenarios where non-admin accounts can unbond others + ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { + // Given the pool is blocked + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + + // A permissionless unbond attempt errors + assert_noop!( + Pools::unbond_other(Origin::signed(420), 100), + Error::::NotKickerOrDestroying + ); + + // Given the pool is destroying + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + + // The depositor cannot be unbonded until they are the last delegator + assert_noop!( + Pools::unbond_other(Origin::signed(420), 10), + Error::::NotOnlyDelegator + ); + + // Any account can unbond a delegator that is not the depositor + assert_ok!(Pools::unbond_other(Origin::signed(420), 100)); + + // Given the pool is blocked + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + + // The depositor cannot be unbonded + assert_noop!( + Pools::unbond_other(Origin::signed(420), 10), + Error::::NotDestroying + ); + + // Given the pools is destroying + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + + // The depositor can be unbonded + assert_ok!(Pools::unbond_other(Origin::signed(420), 10)); + + assert_eq!(BondedPools::::get(&PRIMARY_ACCOUNT).unwrap().points, 0); + assert_eq!( + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { + 0 => UnbondPool { points: 110, balance: 110 } + } + } + ); + assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); + assert_eq!( + UNBONDING_BALANCE_MAP.with(|m| *m.borrow_mut().get(&PRIMARY_ACCOUNT).unwrap()), + 110 + ); + }); + } + #[test] #[should_panic = "Defensive failure has been triggered!"] fn unbond_errors_correctly() { From 9313dfeafc4323f3ca1961cc32e2a7c08f7d72ba Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 9 Feb 2022 22:55:23 -0800 Subject: [PATCH 089/299] Test withdraw unbonded permissionless --- frame/nomination-pools/src/lib.rs | 63 ++++++-- frame/nomination-pools/src/tests.rs | 232 ++++++++++++++++++++++++++-- 2 files changed, 271 insertions(+), 24 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 17989fa80e3df..eca8ac387930f 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -212,7 +212,8 @@ //! //! * Open: Anyone can join the pool and no delegators can be permissionlessly removed. //! * Blocked: No delegators can join and some admin roles can kick delegators. -//! * Destroying: No delegators can join and all delegators can be permissionlessly removed. +//! * Destroying: No delegators can join and all delegators can be permissionlessly removed. Once a +//! pool is destroying state, it cannot be reverted to another state. //! //! A pool has 3 administrative positions (see [`BondedPool`]): //! @@ -251,7 +252,8 @@ // * Every entry in `BondedPoolPoints` must have a corresponding entry in `RewardPools` // * If a delegator unbonds, the sub pools should always correctly track slashses such that the // calculated amount when withdrawing unbonded is a lower bound of the pools free balance. -// * If the depositor is actively unbonding, the pool is in destroying state +// * If the depositor is actively unbonding, the pool is in destroying state. To achieve this, once +// a pool is flipped to a destroying state it cannot change its state. #![cfg_attr(not(feature = "std"), no_std)] @@ -457,6 +459,7 @@ impl BondedPool { /// Check that the pool can accept a member with `new_funds`. fn ok_to_join_with(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { + ensure!(self.state == PoolState::Open, Error::::NotOpen); let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); @@ -516,7 +519,9 @@ impl BondedPool { // Any delegator who is not the depositor can always unbond themselves (true, false) => (), // The depositor can only start unbonding if the pool is already being destroyed and - // they are the delegator in the pool + // they are the delegator in the pool. Note that an invariant is once the pool is + // destroying it cannot switch states, so by being in destroying we are guaranteed no + // other delegators can possibly join. (false, true) | (true, true) => { ensure!(target_delegator.points == self.points, Error::::NotOnlyDelegator); ensure!(self.is_destroying(), Error::::NotDestroying); @@ -525,8 +530,7 @@ impl BondedPool { Ok(()) } - /// Returns a result indicating if `Call::withdraw_unbonded_other`. If the result is ok, the - /// returned value is a bool indicating wether or not to remove the pool from storage. + /// Returns a result indicating if `Call::withdraw_unbonded_other` can be executed. fn ok_to_withdraw_unbonded_other_with( &self, caller: &T::AccountId, @@ -539,23 +543,26 @@ impl BondedPool { if !sub_pools.no_era.points.is_zero() { // Unbonded pool has some points, so if they are the last delegator they must be // here + // Since the depositor is the last to unbond, this should never be possible ensure!(sub_pools.with_era.len().is_zero(), Error::::NotOnlyDelegator); ensure!( sub_pools.no_era.points == target_delegator.points, Error::::NotOnlyDelegator ); } else { - // No points in unbonded pool, so they must be in an unbonding pool + // No points in the `no_era` pool, so they must be in a `with_era` pool + // If there are no other delegators, this can be the only `with_era` pool since the + // depositor was the last to withdraw. This assumes with_era sub pools are destroyed + // whenever their points go to zero. ensure!(sub_pools.with_era.len() == 1, Error::::NotOnlyDelegator); - let only_unbonding_pool = sub_pools + sub_pools .with_era .values() .next() - .expect("ensured at least 1 entry exists on line above. qed."); - ensure!( - only_unbonding_pool.points == target_delegator.points, - Error::::NotOnlyDelegator - ); + .filter(|only_unbonding_pool| { + only_unbonding_pool.points == target_delegator.points + }) + .ok_or(Error::::NotOnlyDelegator)?; } Ok(true) } else { @@ -633,7 +640,7 @@ impl UnbondPool { #[scale_info(skip_type_params(T))] struct SubPools { /// A general, era agnostic pool of funds that have fully unbonded. The pools - /// of `self.with_era` will lazily be merged into into this pool if they are + /// of `Self::with_era` will lazily be merged into into this pool if they are /// older then `current_era - TotalUnbondingPools`. no_era: UnbondPool, /// Map of era => unbond pools. @@ -800,8 +807,8 @@ pub mod pallet { MinimumBondNotMet, /// The transaction could not be executed due to overflow risk for the pool. OverflowRisk, - /// An error from the staking pallet. - StakingError, + /// TODO: remove this + Err, // Likely only an error ever encountered in poorly built tests. /// A pool with the generated account id already exists. IdInUse, @@ -815,6 +822,8 @@ pub mod pallet { NotNominator, /// Either a) the caller cannot make a valid kick or b) the pool is not destroying NotKickerOrDestroying, + /// The pool is not open to join + NotOpen, } #[pallet::call] @@ -825,6 +834,7 @@ pub mod pallet { /// * an account can only be a member of a single pool. /// * this will *not* dust the delegator account, so the delegator must have at least /// `existential deposit + amount` in their account. + /// * Only a pool with [`PoolState::Open`] can be joined #[pallet::weight(666)] #[frame_support::transactional] pub fn join( @@ -1029,6 +1039,10 @@ pub mod pallet { let balance_to_unbond = pool.balance_to_unbond(delegator.points); pool.points = pool.points.saturating_sub(delegator.points); pool.balance = pool.balance.saturating_sub(balance_to_unbond); + if pool.points.is_zero() { + // Clean up pool that is no longer used + sub_pools.with_era.remove(&unbonding_era); + } balance_to_unbond } else { @@ -1170,6 +1184,25 @@ pub mod pallet { T::StakingInterface::nominate(pool_account.clone(), validators)?; Ok(()) } + + // pub fn set_state_other(origin: OriginFor, pool_account: T::AccountId, state: + // PoolState) -> DispatchError { let who = ensure_signed!(origin); + // BondedPool::::try_mutate(pool_account, |maybe_bonded_pool| { + // maybe_bonded_pool.ok_or(Error::::PoolNotFound).map(|bonded_pool| + // if bonded_pool.is_destroying() { + // // invariant, a destroying pool cannot become non-destroying + // // this is because + // Err(Error::::Err)? + // } + + // if bonded_pool.is_spoiled() && state == PoolState::Destroying { + // bonded_pool.state = PoolState::Destroying + // } else if bonded_pool.root == who || bonded_pool.state_toggler == who { + // bonded_pool.state = who + // } + // ) + // }) + // } } #[pallet::hooks] diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 4d2261d494353..47fddd0ac96df 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1673,21 +1673,235 @@ mod withdraw_unbonded_other { Error::::NotUnbondedYet ); - // Skip ahead to the end of the bonding duration - CurrentEra::set(StakingMock::bonding_duration()); - - // TODO - // assert_noop!( - // Pools::withdraw_unbonded_other(Origin::signed(10), 10, 0), - // Error::::NotOnlyDelegator - // ); - // If we error the delegator does not get removed assert_eq!(Delegators::::get(&11), Some(delegator)); // and the sub pools do not get updated. assert_eq!(SubPoolsStorage::::get(123).unwrap(), sub_pools) }); } + + #[test] + fn withdraw_unbonded_other_kick() { + ExtBuilder::default() + .add_delegators(vec![(100, 100), (200, 200)]) + .build_and_execute(|| { + // Given + assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); + assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); + assert_eq!( + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool { + points: 10, + state: PoolState::Open, + depositor: 10, + account: PRIMARY_ACCOUNT, + root: 900, + nominator: 901, + state_toggler: 902, + } + ); + CurrentEra::set(StakingMock::bonding_duration()); + + // Cannot kick when pool is open + assert_noop!( + Pools::withdraw_unbonded_other(Origin::signed(902), 100, 0), + Error::::NotKickerOrDestroying + ); + + // Given + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + + // Cannot kick as a nominator + assert_noop!( + Pools::withdraw_unbonded_other(Origin::signed(901), 100, 0), + Error::::NotKickerOrDestroying + ); + + // Can kick as root + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(900), 100, 0)); + + // Can kick as state toggler + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(900), 200, 0)); + + assert_eq!(Balances::free_balance(100), 100 + 100); + assert_eq!(Balances::free_balance(200), 200 + 200); + assert!(!Delegators::::contains_key(100)); + assert!(!Delegators::::contains_key(200)); + assert_eq!( + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + Default::default() + ); + }); + } + + #[test] + fn withdraw_unbonded_other_destroying_permissionless() { + ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { + // Given + assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); + assert_eq!( + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool { + points: 10, + state: PoolState::Open, + depositor: 10, + account: PRIMARY_ACCOUNT, + root: 900, + nominator: 901, + state_toggler: 902, + } + ); + CurrentEra::set(StakingMock::bonding_duration()); + + // Cannot permissionlessly withdraw + assert_noop!( + Pools::unbond_other(Origin::signed(420), 100), + Error::::NotKickerOrDestroying + ); + + // Given + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + + // Can permissionlesly withdraw a delegator that is not the depositor + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 100, 0)); + + assert_eq!( + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + Default::default(), + ); + assert_eq!(Balances::free_balance(100), 100 + 100); + assert!(!Delegators::::contains_key(100)); + }); + } + + #[test] + fn withdraw_unbonded_other_depositor_with_era_pool() { + ExtBuilder::default() + .add_delegators(vec![(100, 100), (200, 200)]) + .build_and_execute(|| { + // Given + assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); + + let mut current_era = 1; + CurrentEra::set(current_era); + + assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); + + assert_eq!( + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { + 0 => UnbondPool { points: 100, balance: 100}, + 1 => UnbondPool { points: 200 + 10, balance: 200 + 10 } + } + } + ); + // Skip ahead eras to where its valid for the delegators to withdraw + current_era += StakingMock::bonding_duration(); + CurrentEra::set(current_era); + + // Cannot withdraw the depositor if their is a delegator in another `with_era` pool. + assert_noop!( + Pools::withdraw_unbonded_other(Origin::signed(420), 10, 0), + Error::::NotOnlyDelegator + ); + + // Given + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 100, 0)); + assert_eq!( + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { + // Note that era 0 unbond pool is destroyed because points went to 0 + 1 => UnbondPool { points: 200 + 10, balance: 200 + 10 } + } + } + ); + + // Cannot withdraw if their is another delegator in the depositors `with_era` pool + assert_noop!( + Pools::unbond_other(Origin::signed(420), 10), + Error::::NotOnlyDelegator + ); + + // Given + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 200, 0)); + assert_eq!( + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPools { + no_era: Default::default(), + with_era: sub_pools_with_era! { + 1 => UnbondPool { points: 10, balance: 10 } + } + } + ); + + // The depositor can withdraw + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 10, 0)); + assert!(!Delegators::::contains_key(10)); + assert_eq!(Balances::free_balance(10), 10 + 10); + // Pools are removed from storage because the depositor left + assert!(!SubPoolsStorage::::contains_key(&PRIMARY_ACCOUNT),); + assert!(!RewardPools::::contains_key(&PRIMARY_ACCOUNT),); + assert!(!BondedPools::::contains_key(&PRIMARY_ACCOUNT),); + }); + } + + #[test] + fn withdraw_unbonded_other_depositor_no_era_pool() { + ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { + // Given + assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); + Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); + // Skip ahead to an era where the `with_era` pools can get merged into the `no_era` + // pool. + let current_era = TotalUnbondingPools::::get(); + CurrentEra::set(current_era); + + // Simulate some other withdraw that caused the pool to merge + let sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT) + .unwrap() + .maybe_merge_pools(current_era); + SubPoolsStorage::::insert(&PRIMARY_ACCOUNT, sub_pools); + assert_eq!( + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPools { + no_era: UnbondPool { points: 100 + 10, balance: 100 + 10 }, + with_era: Default::default(), + } + ); + + // Cannot withdraw depositor with another delegator in the `no_era` pool + assert_noop!( + Pools::withdraw_unbonded_other(Origin::signed(420), 10, 0), + Error::::NotOnlyDelegator + ); + + // Given + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 100, 0)); + assert_eq!( + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPools { + no_era: UnbondPool { points: 10, balance: 10 }, + with_era: Default::default(), + } + ); + + // The depositor can withdraw + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 10, 0)); + assert!(!Delegators::::contains_key(10)); + assert_eq!(Balances::free_balance(10), 10 + 10); + // Pools are removed from storage because the depositor left + assert!(!SubPoolsStorage::::contains_key(&PRIMARY_ACCOUNT)); + assert!(!RewardPools::::contains_key(&PRIMARY_ACCOUNT)); + assert!(!BondedPools::::contains_key(&PRIMARY_ACCOUNT)); + }); + } } mod create { From d0461ffc8cefb8ecc7ed3ac6c80202b04a0b746f Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 9 Feb 2022 23:07:47 -0800 Subject: [PATCH 090/299] Test only can join open pools --- frame/nomination-pools/src/lib.rs | 2 +- frame/nomination-pools/src/tests.rs | 40 +++++++++++++++++++---------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index eca8ac387930f..46c9586a4aedf 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1404,7 +1404,7 @@ impl Pallet { } #[cfg(test)] - fn set_state(pool_account: &T::AccountId, state: PoolState) -> Result<(), ()> { + fn unsafe_set_state(pool_account: &T::AccountId, state: PoolState) -> Result<(), ()> { BondedPools::::try_mutate(pool_account, |maybe_bonded_pool| { maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { bonded_pool.state = state; diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 47fddd0ac96df..738782596fad7 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -447,6 +447,18 @@ mod join { Pools::join(Origin::signed(11), Balance::MAX / 10 - 100, 123), Error::::OverflowRisk ); + + // Cannot join a pool that isn't open + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + assert_noop!( + Pools::join(Origin::signed(11), 10, PRIMARY_ACCOUNT), + Error::::NotOpen + ); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + assert_noop!( + Pools::join(Origin::signed(11), 10, PRIMARY_ACCOUNT), + Error::::NotOpen + ); }); } @@ -1153,7 +1165,7 @@ mod unbond { #[test] fn unbond_other_of_1_works() { ExtBuilder::default().build_and_execute(|| { - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); assert_eq!( @@ -1213,7 +1225,7 @@ mod unbond { assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding // When - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); // Then @@ -1278,7 +1290,7 @@ mod unbond { }, }, ); - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); // When let current_era = 1 + TotalUnbondingPools::::get(); @@ -1307,7 +1319,7 @@ mod unbond { .add_delegators(vec![(100, 100), (200, 200)]) .build_and_execute(|| { // Given - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); assert_eq!(bonded_pool.root, 900); assert_eq!(bonded_pool.nominator, 901); @@ -1359,7 +1371,7 @@ mod unbond { // Scenarios where non-admin accounts can unbond others ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given the pool is blocked - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); // A permissionless unbond attempt errors assert_noop!( @@ -1368,7 +1380,7 @@ mod unbond { ); // Given the pool is destroying - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); // The depositor cannot be unbonded until they are the last delegator assert_noop!( @@ -1380,7 +1392,7 @@ mod unbond { assert_ok!(Pools::unbond_other(Origin::signed(420), 100)); // Given the pool is blocked - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); // The depositor cannot be unbonded assert_noop!( @@ -1389,7 +1401,7 @@ mod unbond { ); // Given the pools is destroying - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); // The depositor can be unbonded assert_ok!(Pools::unbond_other(Origin::signed(420), 10)); @@ -1497,7 +1509,7 @@ mod withdraw_unbonded_other { let mut current_era = 1; CurrentEra::set(current_era); // In a new era, unbond the depositor - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); let mut sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(); @@ -1583,7 +1595,7 @@ mod withdraw_unbonded_other { assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); SubPoolsStorage::::insert( @@ -1709,7 +1721,7 @@ mod withdraw_unbonded_other { ); // Given - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); // Cannot kick as a nominator assert_noop!( @@ -1760,7 +1772,7 @@ mod withdraw_unbonded_other { ); // Given - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); // Can permissionlesly withdraw a delegator that is not the depositor assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 100, 0)); @@ -1786,7 +1798,7 @@ mod withdraw_unbonded_other { CurrentEra::set(current_era); assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); assert_eq!( @@ -1856,7 +1868,7 @@ mod withdraw_unbonded_other { ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); - Pools::set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); // Skip ahead to an era where the `with_era` pools can get merged into the `no_era` // pool. From 69154331e53c423b5713294f0549606783652d35 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 10 Feb 2022 14:41:58 -0800 Subject: [PATCH 091/299] Move unsafe set state to mock --- frame/nomination-pools/src/lib.rs | 9 ------- frame/nomination-pools/src/mock.rs | 9 +++++++ frame/nomination-pools/src/tests.rs | 37 +++++++++++++++-------------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 46c9586a4aedf..cfbc25ee442e7 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1402,15 +1402,6 @@ impl Pallet { }; Some(SlashPoolOut { slashed_bonded, slashed_unlocking }) } - - #[cfg(test)] - fn unsafe_set_state(pool_account: &T::AccountId, state: PoolState) -> Result<(), ()> { - BondedPools::::try_mutate(pool_account, |maybe_bonded_pool| { - maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { - bonded_pool.state = state; - }) - }) - } } impl PoolsInterface for Pallet { diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 988ce83f04f72..3a33da2b6e535 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -218,6 +218,15 @@ impl ExtBuilder { } } +#[cfg(test)] +pub(crate) fn unsafe_set_state(pool_account: &AccountId, state: PoolState) -> Result<(), ()> { + BondedPools::::try_mutate(pool_account, |maybe_bonded_pool| { + maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { + bonded_pool.state = state; + }) + }) +} + #[cfg(test)] mod test { use super::*; diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 738782596fad7..b365b1672515d 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1,7 +1,8 @@ use super::*; use crate::mock::{ - Balance, Balances, BondingDuration, CurrentEra, ExistentialDeposit, ExtBuilder, Origin, Pools, - Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, UNBONDING_BALANCE_MAP, + unsafe_set_state, Balance, Balances, BondingDuration, CurrentEra, ExistentialDeposit, + ExtBuilder, Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, + UNBONDING_BALANCE_MAP, }; use frame_support::{assert_noop, assert_ok}; @@ -449,12 +450,12 @@ mod join { ); // Cannot join a pool that isn't open - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); assert_noop!( Pools::join(Origin::signed(11), 10, PRIMARY_ACCOUNT), Error::::NotOpen ); - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_noop!( Pools::join(Origin::signed(11), 10, PRIMARY_ACCOUNT), Error::::NotOpen @@ -1165,7 +1166,7 @@ mod unbond { #[test] fn unbond_other_of_1_works() { ExtBuilder::default().build_and_execute(|| { - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); assert_eq!( @@ -1225,7 +1226,7 @@ mod unbond { assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding // When - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); // Then @@ -1290,7 +1291,7 @@ mod unbond { }, }, ); - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); // When let current_era = 1 + TotalUnbondingPools::::get(); @@ -1319,7 +1320,7 @@ mod unbond { .add_delegators(vec![(100, 100), (200, 200)]) .build_and_execute(|| { // Given - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); assert_eq!(bonded_pool.root, 900); assert_eq!(bonded_pool.nominator, 901); @@ -1371,7 +1372,7 @@ mod unbond { // Scenarios where non-admin accounts can unbond others ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given the pool is blocked - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); // A permissionless unbond attempt errors assert_noop!( @@ -1380,7 +1381,7 @@ mod unbond { ); // Given the pool is destroying - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); // The depositor cannot be unbonded until they are the last delegator assert_noop!( @@ -1392,7 +1393,7 @@ mod unbond { assert_ok!(Pools::unbond_other(Origin::signed(420), 100)); // Given the pool is blocked - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); // The depositor cannot be unbonded assert_noop!( @@ -1401,7 +1402,7 @@ mod unbond { ); // Given the pools is destroying - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); // The depositor can be unbonded assert_ok!(Pools::unbond_other(Origin::signed(420), 10)); @@ -1509,7 +1510,7 @@ mod withdraw_unbonded_other { let mut current_era = 1; CurrentEra::set(current_era); // In a new era, unbond the depositor - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); let mut sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(); @@ -1595,7 +1596,7 @@ mod withdraw_unbonded_other { assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); SubPoolsStorage::::insert( @@ -1721,7 +1722,7 @@ mod withdraw_unbonded_other { ); // Given - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); // Cannot kick as a nominator assert_noop!( @@ -1772,7 +1773,7 @@ mod withdraw_unbonded_other { ); // Given - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); // Can permissionlesly withdraw a delegator that is not the depositor assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 100, 0)); @@ -1798,7 +1799,7 @@ mod withdraw_unbonded_other { CurrentEra::set(current_era); assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); assert_eq!( @@ -1868,7 +1869,7 @@ mod withdraw_unbonded_other { ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); - Pools::unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); // Skip ahead to an era where the `with_era` pools can get merged into the `no_era` // pool. From 2a0b068682eabf0b7503951493a6463d69422615 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 10 Feb 2022 15:03:18 -0800 Subject: [PATCH 092/299] Test: nominate_works --- frame/nomination-pools/src/lib.rs | 4 +--- frame/nomination-pools/src/mock.rs | 4 +++- frame/nomination-pools/src/tests.rs | 37 ++++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index cfbc25ee442e7..5c394f3bf135b 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -257,9 +257,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -// TODO: -// - test permissionless use cases of unbond and withdraw unbonded -// - test depositor unbond and unbond withdraws +// TODO // - test nominate // - checks for number of pools when creating pools (param for max pools, pool creation origin) // - post checks that rewardpool::count == bondedpool::count. delegators >= bondedpool::count, diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 3a33da2b6e535..f84cd83c48c0e 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -16,6 +16,7 @@ parameter_types! { static BondedBalanceMap: std::collections::HashMap = Default::default(); static UnbondingBalanceMap: std::collections::HashMap = Default::default(); pub static BondingDuration: EraIndex = 3; + pub static Nominations: Vec = vec![]; } pub struct StakingMock; @@ -76,7 +77,8 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } - fn nominate(_: Self::AccountId, _: Vec) -> DispatchResult { + fn nominate(_: Self::AccountId, nominations: Vec) -> DispatchResult { + Nominations::set(nominations); Ok(()) } } diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index b365b1672515d..3c3ec172df865 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1,7 +1,7 @@ use super::*; use crate::mock::{ unsafe_set_state, Balance, Balances, BondingDuration, CurrentEra, ExistentialDeposit, - ExtBuilder, Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, + ExtBuilder, Nominations, Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, UNBONDING_BALANCE_MAP, }; use frame_support::{assert_noop, assert_ok}; @@ -2195,3 +2195,38 @@ mod pools_interface { } // TODO: returns none when account is not a pool } + +mod nominate { + use super::*; + + #[test] + fn nominate_works() { + ExtBuilder::default().build_and_execute(|| { + // Depositor can't nominate + assert_noop!( + Pools::nominate(Origin::signed(10), PRIMARY_ACCOUNT, vec![21]), + Error::::NotNominator + ); + + // State toggler can't nominate + assert_noop!( + Pools::nominate(Origin::signed(902), PRIMARY_ACCOUNT, vec![21]), + Error::::NotNominator + ); + + // Root can nominate + assert_ok!(Pools::nominate(Origin::signed(900), PRIMARY_ACCOUNT, vec![21])); + assert_eq!(Nominations::get(), vec![21]); + + // Nominator can nominate + assert_ok!(Pools::nominate(Origin::signed(901), PRIMARY_ACCOUNT, vec![31])); + assert_eq!(Nominations::get(), vec![31]); + + // Can't nominate for a pool that doesn't exist + assert_noop!( + Pools::nominate(Origin::signed(902), 123, vec![21]), + Error::::PoolNotFound + ); + }); + } +} From eded4bd784e1e898cd6db4b49edc4423133d797f Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 10 Feb 2022 16:17:06 -0800 Subject: [PATCH 093/299] Add bounds: MinJoinBond, MinCreateBond, MaxPools --- frame/nomination-pools/src/lib.rs | 50 ++++++++++++++++++++++++++---- frame/nomination-pools/src/mock.rs | 11 ++++++- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 5c394f3bf135b..8ffd6560ab4da 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -258,7 +258,6 @@ #![cfg_attr(not(feature = "std"), no_std)] // TODO -// - test nominate // - checks for number of pools when creating pools (param for max pools, pool creation origin) // - post checks that rewardpool::count == bondedpool::count. delegators >= bondedpool::count, // subpools::count <= bondedpools @@ -746,6 +745,19 @@ pub mod pallet { type PostUnbondingPoolsWindow: Get; } + /// Minimum amount to bond to join a pool. + #[pallet::storage] + pub(crate) type MinJoinBond = StorageValue<_, BalanceOf, ValueQuery>; + + /// Minimum bond required to create a pool. + #[pallet::storage] + pub(crate) type MinCreateBond = StorageValue<_, BalanceOf, ValueQuery>; + + /// Maximum number of nomination pools that can exist. If `None`, then an unbounded number of + /// pools can exist. + #[pallet::storage] + pub(crate) type MaxPools = StorageValue<_, u32, OptionQuery>; + /// Active delegators. #[pallet::storage] pub(crate) type Delegators = @@ -769,6 +781,25 @@ pub mod pallet { pub(crate) type SubPoolsStorage = CountedStorageMap<_, Twox64Concat, T::AccountId, SubPools>; + #[pallet::genesis_config] + #[cfg_attr(feature = "std", derive(DefaultNoBound))] + pub struct GenesisConfig { + pub min_join_bond: BalanceOf, + pub min_create_bond: BalanceOf, + pub max_pools: Option, + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + MinJoinBond::::put(self.min_join_bond); + MinCreateBond::::put(self.min_create_bond); + if let Some(max_pools) = self.max_pools { + MaxPools::::put(max_pools); + } + } + } + #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { @@ -801,7 +832,7 @@ pub mod pallet { NotUnbonding, /// Unbonded funds cannot be withdrawn yet because the bond duration has not passed. NotUnbondedYet, - /// The amount does not meet the minimum bond to start nominating. + /// The amount does not meet the minimum bond to either join or create a pool. MinimumBondNotMet, /// The transaction could not be executed due to overflow risk for the pool. OverflowRisk, @@ -822,6 +853,8 @@ pub mod pallet { NotKickerOrDestroying, /// The pool is not open to join NotOpen, + /// The system is maxed out on pools. + MaxPools, } #[pallet::call] @@ -841,6 +874,7 @@ pub mod pallet { pool_account: T::AccountId, ) -> DispatchResult { let who = ensure_signed(origin)?; + ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); // If a delegator already exists that means they already belong to a pool ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); @@ -1116,10 +1150,14 @@ pub mod pallet { state_toggler: T::AccountId, ) -> DispatchResult { let who = ensure_signed(origin)?; - // TODO: this should be min some deposit - // TODO: have an integrity test that min deposit is at least min bond (make min deposit - // storage item so it can be increased) - ensure!(amount >= T::StakingInterface::minimum_bond(), Error::::MinimumBondNotMet); + ensure!( + amount >= T::StakingInterface::minimum_bond() && + amount >= MinCreateBond::::get(), + Error::::MinimumBondNotMet + ); + if let Some(max_pools) = MaxPools::::get() { + ensure!(BondedPools::::count() as u32 <= max_pools, Error::::MaxPools); + } ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); let (pool_account, reward_account) = Self::create_accounts(index); diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index f84cd83c48c0e..45332ced0c319 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -17,6 +17,7 @@ parameter_types! { static UnbondingBalanceMap: std::collections::HashMap = Default::default(); pub static BondingDuration: EraIndex = 3; pub static Nominations: Vec = vec![]; + pub static MaxPools: u32 = 3; } pub struct StakingMock; @@ -181,7 +182,15 @@ impl ExtBuilder { pub(crate) fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); - let storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut storage = + frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let _ = crate::GenesisConfig:: { + min_join_bond: 2, + min_create_bond: 2, + max_pools: Some(2), + } + .assimilate_storage(&mut storage); let mut ext = sp_io::TestExternalities::from(storage); From d93632145ea7e5ec76723a9cf6e857a50aeef428 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 10 Feb 2022 16:34:57 -0800 Subject: [PATCH 094/299] Test MinCreateBond, MinJoinBond, MaxPools --- frame/nomination-pools/src/lib.rs | 2 +- frame/nomination-pools/src/mock.rs | 1 - frame/nomination-pools/src/tests.rs | 43 ++++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 8ffd6560ab4da..bdf4a841add8d 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1156,7 +1156,7 @@ pub mod pallet { Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { - ensure!(BondedPools::::count() as u32 <= max_pools, Error::::MaxPools); + ensure!((BondedPools::::count() as u32) < max_pools, Error::::MaxPools); } ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 45332ced0c319..39c2deab139d5 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -17,7 +17,6 @@ parameter_types! { static UnbondingBalanceMap: std::collections::HashMap = Default::default(); pub static BondingDuration: EraIndex = 3; pub static Nominations: Vec = vec![]; - pub static MaxPools: u32 = 3; } pub struct StakingMock; diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 3c3ec172df865..14ecb61b094cd 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -460,6 +460,15 @@ mod join { Pools::join(Origin::signed(11), 10, PRIMARY_ACCOUNT), Error::::NotOpen ); + + // Given + MinJoinBond::::put(100); + + // Then + assert_noop!( + Pools::join(Origin::signed(11), 99, 123), + Error::::MinimumBondNotMet + ); }); } @@ -1984,10 +1993,42 @@ mod create { Error::::IdInUse ); + // Given + assert_eq!(MinCreateBond::::get(), 2); + assert_eq!(StakingMock::minimum_bond(), 10); + + // Then assert_noop!( - Pools::create(Origin::signed(11), 1, 42, 123, 456, 789), + Pools::create(Origin::signed(11), 9, 42, 123, 456, 789), Error::::MinimumBondNotMet ); + + // Given + MinCreateBond::::put(20); + + // Then + assert_noop!( + Pools::create(Origin::signed(11), 19, 42, 123, 456, 789), + Error::::MinimumBondNotMet + ); + + BondedPool:: { + depositor: 10, + state: PoolState::Open, + points: 10, + account: 123, + root: 900, + nominator: 901, + state_toggler: 902, + } + .put(); + assert_eq!(MaxPools::::get(), Some(2)); + assert_eq!(BondedPools::::count(), 2); + + assert_noop!( + Pools::create(Origin::signed(11), 20, 42, 123, 456, 789), + Error::::MaxPools + ); }); } } From 532cebe364f57ef219c7022b1978a14bc986f02a Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 10 Feb 2022 16:43:15 -0800 Subject: [PATCH 095/299] Add post checks to tests --- frame/nomination-pools/src/lib.rs | 7 ------- frame/nomination-pools/src/mock.rs | 14 +++++++++++++- frame/nomination-pools/src/tests.rs | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index bdf4a841add8d..6ee236c329070 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -257,11 +257,6 @@ #![cfg_attr(not(feature = "std"), no_std)] -// TODO -// - checks for number of pools when creating pools (param for max pools, pool creation origin) -// - post checks that rewardpool::count == bondedpool::count. delegators >= bondedpool::count, -// subpools::count <= bondedpools - use frame_support::{ ensure, pallet_prelude::*, @@ -836,8 +831,6 @@ pub mod pallet { MinimumBondNotMet, /// The transaction could not be executed due to overflow risk for the pool. OverflowRisk, - /// TODO: remove this - Err, // Likely only an error ever encountered in poorly built tests. /// A pool with the generated account id already exists. IdInUse, diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 39c2deab139d5..5b9a4ba42ef20 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -223,9 +223,21 @@ impl ExtBuilder { pub fn build_and_execute(self, test: impl FnOnce() -> ()) { self.build().execute_with(|| { test(); - // post-checks can be added here + post_checks(); }) } + + pub fn build_and_execute_no_checks(self, test: impl FnOnce() -> ()) { + self.build().execute_with(|| { + test(); + }) + } +} + +fn post_checks() { + assert_eq!(RewardPools::::count(), BondedPools::::count()); + assert!(SubPoolsStorage::::count() <= BondedPools::::count()); + assert!(Delegators::::count() >= BondedPools::::count()); } #[cfg(test)] diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 14ecb61b094cd..059e725b3cd3c 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -412,7 +412,7 @@ mod join { // TODO: test transactional storage aspect #[test] fn join_errors_correctly() { - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().build_and_execute_no_checks(|| { assert_noop!( Pools::join(Origin::signed(10), 420, 420), Error::::AccountBelongsToOtherPool @@ -1987,7 +1987,7 @@ mod create { // TODO check transactional storage aspect #[test] fn create_errors_correctly() { - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().build_and_execute_no_checks(|| { assert_noop!( Pools::create(Origin::signed(11), 420, 0, 123, 456, 789), Error::::IdInUse From 03d37d54af73e7852aa312e82179c6c0b361d527 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 10 Feb 2022 16:44:00 -0800 Subject: [PATCH 096/299] Remove some TODOs --- frame/nomination-pools/src/tests.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 059e725b3cd3c..793d34f4b9572 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -409,7 +409,6 @@ mod join { }); } - // TODO: test transactional storage aspect #[test] fn join_errors_correctly() { ExtBuilder::default().build_and_execute_no_checks(|| { @@ -1984,7 +1983,6 @@ mod create { }); } - // TODO check transactional storage aspect #[test] fn create_errors_correctly() { ExtBuilder::default().build_and_execute_no_checks(|| { @@ -2234,7 +2232,6 @@ mod pools_interface { assert_eq!(slashed_bonded, 0); }); } - // TODO: returns none when account is not a pool } mod nominate { From b2e66f92cccbb44b0cfa3d4aa06287fc9d08101b Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 10 Feb 2022 17:14:46 -0800 Subject: [PATCH 097/299] Setup weight infrastructure --- Cargo.lock | 1 + frame/nomination-pools/Cargo.toml | 8 +++++ frame/nomination-pools/src/benchmarks.rs | 10 +++++++ frame/nomination-pools/src/lib.rs | 9 ++++-- frame/nomination-pools/src/mock.rs | 1 + frame/nomination-pools/src/weights.rs | 37 ++++++++++++++++++++++++ 6 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 frame/nomination-pools/src/benchmarks.rs create mode 100644 frame/nomination-pools/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 003d347c3829e..ffb2439321bb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6040,6 +6040,7 @@ dependencies = [ name = "pallet-nomination-pools" version = "0.0.1" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "log 0.4.14", diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 210424196fd13..5c745d2321965 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -28,6 +28,10 @@ sp-core = { version = "5.0.0", default-features = false, path = "../../primitive # third party log = { version = "0.4.14", default-features = false } +# Optional imports for benchmarking +frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking", optional = true, default-features = false } +pallet-balances = { version = "4.0.0-dev", path = "../balances", optional = true, default-features = false } + [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } sp-io = { version = "5.0.0", path = "../../primitives/io"} @@ -41,3 +45,7 @@ std = [ "frame-system/std", "sp-arithmetic/std", ] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "pallet-balances", +] diff --git a/frame/nomination-pools/src/benchmarks.rs b/frame/nomination-pools/src/benchmarks.rs new file mode 100644 index 0000000000000..2a36162e9090b --- /dev/null +++ b/frame/nomination-pools/src/benchmarks.rs @@ -0,0 +1,10 @@ +use super::*; +frame_benchmarking::benchmarks! { + join {}: {} + claim_payout {}: {} + unbond_other {}: {} + pool_withdraw_unbonded {}: {} + withdraw_unbonded_other {}: {} + create {}: {} + nominate {}: {} +} diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 6ee236c329070..7f903e91d1505 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -270,12 +270,17 @@ use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZer use sp_staking::{EraIndex, PoolsInterface, SlashPoolArgs, SlashPoolOut, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div}; +#[cfg(feature = "runtime-benchmarks")] +mod benchmarks; + #[cfg(test)] mod mock; #[cfg(test)] mod tests; +pub mod weights; pub use pallet::*; +pub use weights::WeightInfo; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -714,7 +719,7 @@ pub mod pallet { type Event: From> + IsType<::Event>; /// Weight information for extrinsics in this pallet. - // type WeightInfo: weights::WeightInfo; + type WeightInfo: weights::WeightInfo; /// The nominating balance. type Currency: Currency; @@ -923,7 +928,7 @@ pub mod pallet { /// time claiming rewards). /// /// Note that the payout will go to the delegator's account. - #[pallet::weight(666)] + #[pallet::weight(T::WeightInfo::claim_payout())] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 5b9a4ba42ef20..7187066c29fda 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -146,6 +146,7 @@ parameter_types! { impl pools::Config for Runtime { type Event = Event; + type WeightInfo = (); type Currency = Balances; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs new file mode 100644 index 0000000000000..db31e0ddfd367 --- /dev/null +++ b/frame/nomination-pools/src/weights.rs @@ -0,0 +1,37 @@ +use frame_support::weights::Weight; + +/// Weight functions needed for pallet_bags_list. +pub trait WeightInfo { + fn join() -> Weight; + fn claim_payout() -> Weight; + fn unbond_other() -> Weight; + fn pool_withdraw_unbonded() -> Weight; + fn withdraw_unbonded_other() -> Weight; + fn create() -> Weight; + fn nominate() -> Weight; +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn join() -> Weight { + Weight::MAX + } + fn claim_payout() -> Weight { + Weight::MAX + } + fn unbond_other() -> Weight { + Weight::MAX + } + fn pool_withdraw_unbonded() -> Weight { + Weight::MAX + } + fn withdraw_unbonded_other() -> Weight { + Weight::MAX + } + fn create() -> Weight { + Weight::MAX + } + fn nominate() -> Weight { + Weight::MAX + } +} From 08e4417159c9d0781334253635c4898b2e7ed447 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 10 Feb 2022 19:05:45 -0800 Subject: [PATCH 098/299] Benchmark claim_payout --- frame/nomination-pools/Cargo.toml | 5 +- frame/nomination-pools/src/benchmarking.rs | 65 ++++++++++++++++++++++ frame/nomination-pools/src/benchmarks.rs | 10 ---- frame/nomination-pools/src/lib.rs | 2 +- 4 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 frame/nomination-pools/src/benchmarking.rs delete mode 100644 frame/nomination-pools/src/benchmarks.rs diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 5c745d2321965..547f7e2f13449 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -46,6 +46,9 @@ std = [ "sp-arithmetic/std", ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", "pallet-balances", ] diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs new file mode 100644 index 0000000000000..b2842b0e1e47c --- /dev/null +++ b/frame/nomination-pools/src/benchmarking.rs @@ -0,0 +1,65 @@ +use super::*; +use crate::Pallet as Pools; +use frame_benchmarking::account; +use frame_system::RawOrigin as Origin; +use frame_support::{assert_ok}; + +const USER_SEED: u32 = 0; + +frame_benchmarking::benchmarks! { + join {}: {} + claim_payout { + let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); + let depositor = account("depositor", USER_SEED, 0); + + // Create a pool + T::Currency::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); + assert_ok!( + Pools::::create( + Origin::Signed(depositor.clone()).into(), + min_create_bond, + 0, + depositor.clone(), + depositor.clone(), + depositor.clone() + ) + ); + + let reward_account = RewardPools::::get( + BondedPools::::iter().next().unwrap().0 + ) + .unwrap() + .account; + + // Send funds to the reward account of the pool + T::Currency::make_free_balance_be(&reward_account, min_create_bond); + + // Sanity check + assert_eq!( + T::Currency::free_balance(&depositor), + min_create_bond + ); + }:_(Origin::Signed(depositor.clone())) + verify { + assert_eq!( + T::Currency::free_balance(&depositor), + min_create_bond * 2u32.into() + ); + assert_eq!( + T::Currency::free_balance(&reward_account), + Zero::zero() + ); + } + + unbond_other {}: {} + pool_withdraw_unbonded {}: {} + withdraw_unbonded_other {}: {} + create {}: {} + nominate {}: {} +} + +frame_benchmarking::impl_benchmark_test_suite!( + Pallet, + crate::mock::ExtBuilder::default().build(), + crate::mock::Runtime +); \ No newline at end of file diff --git a/frame/nomination-pools/src/benchmarks.rs b/frame/nomination-pools/src/benchmarks.rs deleted file mode 100644 index 2a36162e9090b..0000000000000 --- a/frame/nomination-pools/src/benchmarks.rs +++ /dev/null @@ -1,10 +0,0 @@ -use super::*; -frame_benchmarking::benchmarks! { - join {}: {} - claim_payout {}: {} - unbond_other {}: {} - pool_withdraw_unbonded {}: {} - withdraw_unbonded_other {}: {} - create {}: {} - nominate {}: {} -} diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 7f903e91d1505..a41a0eafd1ecd 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -271,7 +271,7 @@ use sp_staking::{EraIndex, PoolsInterface, SlashPoolArgs, SlashPoolOut, StakingI use sp_std::{collections::btree_map::BTreeMap, ops::Div}; #[cfg(feature = "runtime-benchmarks")] -mod benchmarks; +pub mod benchmarking; #[cfg(test)] mod mock; From ba9b078a6acebf0953738f2c8f58e1887816feee Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 11 Feb 2022 13:37:53 -0800 Subject: [PATCH 099/299] Benchmark create --- frame/nomination-pools/src/benchmarking.rs | 49 ++++++++++++++++++++-- frame/nomination-pools/src/lib.rs | 7 ++++ frame/nomination-pools/src/mock.rs | 4 ++ frame/staking/src/mock.rs | 22 ++++++++++ frame/staking/src/pallet/impls.rs | 5 +++ primitives/staking/src/lib.rs | 4 ++ 6 files changed, 87 insertions(+), 4 deletions(-) diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index b2842b0e1e47c..41c0d69af5108 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -1,8 +1,8 @@ use super::*; use crate::Pallet as Pools; use frame_benchmarking::account; +use frame_support::assert_ok; use frame_system::RawOrigin as Origin; -use frame_support::{assert_ok}; const USER_SEED: u32 = 0; @@ -24,9 +24,11 @@ frame_benchmarking::benchmarks! { depositor.clone() ) ); + // index 0 of the tuple is the key, the pool account + let pool_account = BondedPools::::iter().next().unwrap().0; let reward_account = RewardPools::::get( - BondedPools::::iter().next().unwrap().0 + pool_account ) .unwrap() .account; @@ -54,7 +56,46 @@ frame_benchmarking::benchmarks! { unbond_other {}: {} pool_withdraw_unbonded {}: {} withdraw_unbonded_other {}: {} - create {}: {} + + create { + let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); + let depositor: T::AccountId = account("depositor", USER_SEED, 0); + + // Give the depositor some balance to bond + T::Currency::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); + + // Make sure no pools exist as a pre-condition for our verify checks + assert_eq!(RewardPools::::count(), 0); + assert_eq!(BondedPools::::count(), 0); + }: _( + Origin::Signed(depositor.clone()), + min_create_bond, + 0, + depositor.clone(), + depositor.clone(), + depositor.clone() + ) + verify { + assert_eq!(RewardPools::::count(), 1); + assert_eq!(BondedPools::::count(), 1); + let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); + assert_eq!( + new_pool, + BondedPoolStorage { + points: min_create_bond, + depositor: depositor.clone(), + root: depositor.clone(), + nominator: depositor.clone(), + state_toggler: depositor.clone(), + state: PoolState::Open, + } + ); + assert_eq!( + T::StakingInterface::bonded_balance(&pool_account), + Some(min_create_bond) + ); + } + nominate {}: {} } @@ -62,4 +103,4 @@ frame_benchmarking::impl_benchmark_test_suite!( Pallet, crate::mock::ExtBuilder::default().build(), crate::mock::Runtime -); \ No newline at end of file +); diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index a41a0eafd1ecd..4b013446c9777 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -255,6 +255,13 @@ // * If the depositor is actively unbonding, the pool is in destroying state. To achieve this, once // a pool is flipped to a destroying state it cannot change its state. +// TODO +// - Write user top level docs and make the design docs internal +// - Refactor staking slashing to always slash unlocking chunks (then back port) +// - backport making ledger generic over ^^ IDEA: maybe staking can slash unlocking chunks, and then +// pools is passed the updated unlocking chunks and makes updates based on that +// - benchmarks + #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 7187066c29fda..be217a1b8046e 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -81,6 +81,10 @@ impl sp_staking::StakingInterface for StakingMock { Nominations::set(nominations); Ok(()) } + + // fn setup_nominate_scenario() -> Vec { + // vec![1] + // } } impl frame_system::Config for Runtime { diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 62292608dcd31..0231747b33992 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -870,3 +870,25 @@ pub(crate) fn staking_events() -> Vec> { pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) { (Balances::free_balance(who), Balances::reserved_balance(who)) } + +// fn do_nominate_bench_setup() -> Vec<> { +// // clean up any existing state. +// clear_validators_and_nominators::(); + +// let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + +// // setup a worst case list scenario. Note we don't care about the destination position, because +// // we are just doing an insert into the origin position. +// let scenario = ListScenario::::new(origin_weight, true)?; +// let (stash, controller) = create_stash_controller_with_balance::( +// SEED + T::MaxNominations::get() + 1, // make sure the account does not conflict with others +// origin_weight, +// Default::default(), +// ).unwrap(); + +// assert!(!Nominators::::contains_key(&stash)); +// assert!(!T::SortedListProvider::contains(&stash)); + +// let validators = create_validators::(T::MaxNominations::get(), 100).unwrap(); +// return validators +// } \ No newline at end of file diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index a44507fa4fb7a..9b28c3e2596f3 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1374,4 +1374,9 @@ impl StakingInterface for Pallet { fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult { Self::nominate(RawOrigin::Signed(controller).into(), targets) } + + #[cfg(feature = "runtime-benchmarks")] + fn setup_nominate_scenario() -> Vec { + + } } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index c03a2b1d898ec..453fc90b93a84 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -122,4 +122,8 @@ pub trait StakingInterface { ) -> DispatchResult; fn nominate(stash: Self::AccountId, targets: Vec) -> DispatchResult; + + // Benchmarking helpers + + // fn setup_nominate_scenario() -> (Self::AccountId, Self::AccountId, Vec); } From ef69609ad50bf280a9684f6b8892f9314ec3317a Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 11 Feb 2022 14:23:19 -0800 Subject: [PATCH 100/299] Benchmark nominate --- frame/nomination-pools/src/benchmarking.rs | 47 +++++++++++++++++++++- frame/nomination-pools/src/lib.rs | 2 +- frame/nomination-pools/src/mock.rs | 6 +-- frame/staking/src/mock.rs | 2 +- frame/staking/src/pallet/impls.rs | 4 +- primitives/staking/src/lib.rs | 2 + 6 files changed, 55 insertions(+), 8 deletions(-) diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index 41c0d69af5108..75eb34b734c3f 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -96,7 +96,52 @@ frame_benchmarking::benchmarks! { ); } - nominate {}: {} + nominate { + let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); + let depositor = account("depositor", USER_SEED, 0); + + // Create a pool + T::Currency::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); + assert_ok!( + Pools::::create( + Origin::Signed(depositor.clone()).into(), + min_create_bond, + 0, + depositor.clone(), + depositor.clone(), + depositor.clone() + ) + ); + // index 0 of the tuple is the key, the pool account + let pool_account = BondedPools::::iter().next().unwrap().0; + + // Create some accounts to nominate. For the sake of benchmarking they don't need to be actual validators + let validators: Vec<_> = (0..T::StakingInterface::max_nominations()) + .map(|i| + T::Lookup::unlookup(account("stash", USER_SEED, i)) + ) + .collect(); + }:_(Origin::Signed(depositor.clone()), pool_account, validators) + verify { + assert_eq!(RewardPools::::count(), 1); + assert_eq!(BondedPools::::count(), 1); + let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); + assert_eq!( + new_pool, + BondedPoolStorage { + points: min_create_bond, + depositor: depositor.clone(), + root: depositor.clone(), + nominator: depositor.clone(), + state_toggler: depositor.clone(), + state: PoolState::Open, + } + ); + assert_eq!( + T::StakingInterface::bonded_balance(&pool_account), + Some(min_create_bond) + ); + } } frame_benchmarking::impl_benchmark_test_suite!( diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 4b013446c9777..1a605bfa5410b 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1212,7 +1212,7 @@ pub mod pallet { Ok(()) } - #[pallet::weight(666)] + #[pallet::weight(T::WeightInfo::nominate())] pub fn nominate( origin: OriginFor, pool_account: T::AccountId, diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index be217a1b8046e..bdfe55c1fbab4 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -82,9 +82,9 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } - // fn setup_nominate_scenario() -> Vec { - // vec![1] - // } + fn max_nominations() -> u32 { + 3 + } } impl frame_system::Config for Runtime { diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 0231747b33992..38a20d7ce3102 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -891,4 +891,4 @@ pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) { // let validators = create_validators::(T::MaxNominations::get(), 100).unwrap(); // return validators -// } \ No newline at end of file +// } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 9b28c3e2596f3..50bdf85e99f2e 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1376,7 +1376,7 @@ impl StakingInterface for Pallet { } #[cfg(feature = "runtime-benchmarks")] - fn setup_nominate_scenario() -> Vec { - + fn max_nominations() -> u32 { + T::MaxNominations::get() } } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 453fc90b93a84..768a3a3a14196 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -125,5 +125,7 @@ pub trait StakingInterface { // Benchmarking helpers + fn max_nominations() -> u32; + // fn setup_nominate_scenario() -> (Self::AccountId, Self::AccountId, Vec); } From fe935fb1f1a2f830fbb260333c10cbfc5d0a0082 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 11 Feb 2022 14:51:42 -0800 Subject: [PATCH 101/299] Benchmark join --- frame/nomination-pools/src/benchmarking.rs | 62 +++++++++++++++++++++- frame/nomination-pools/src/mock.rs | 8 ++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index 75eb34b734c3f..cfbd472c0f2db 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -1,14 +1,62 @@ use super::*; use crate::Pallet as Pools; -use frame_benchmarking::account; +use frame_benchmarking::{account, whitelist_account}; use frame_support::assert_ok; use frame_system::RawOrigin as Origin; +use crate::mock::clear_storage; const USER_SEED: u32 = 0; frame_benchmarking::benchmarks! { - join {}: {} + join { + clear_storage::(); + + let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); + let depositor = account("depositor", USER_SEED, 0); + + // Create a pool + T::Currency::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); + assert_ok!( + Pools::::create( + Origin::Signed(depositor.clone()).into(), + min_create_bond, + 0, + depositor.clone(), + depositor.clone(), + depositor.clone() + ) + ); + // index 0 of the tuple is the key, the pool account + let pool_account = BondedPools::::iter().next().unwrap().0; + + // Create the account that will join the pool + let joiner = account("joiner", USER_SEED, 0); + let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); + // and give it some funds. + T::Currency::make_free_balance_be(&joiner, min_join_bond * 2u32.into()); + + whitelist_account!(joiner); + }: _(Origin::Signed(joiner.clone()), min_join_bond, pool_account.clone()) + verify { + assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); + assert_eq!(T::StakingInterface::bonded_balance(&pool_account), Some(min_join_bond + min_create_bond)); + assert_eq!( + BondedPool::::get(&pool_account).unwrap(), + BondedPool { + account: pool_account, + points: min_join_bond + min_create_bond, + depositor: depositor.clone(), + root: depositor.clone(), + nominator: depositor.clone(), + state_toggler: depositor.clone(), + state: PoolState::Open, + } + ); + } + claim_payout { + clear_storage::(); + let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); let depositor = account("depositor", USER_SEED, 0); @@ -41,6 +89,8 @@ frame_benchmarking::benchmarks! { T::Currency::free_balance(&depositor), min_create_bond ); + + whitelist_account!(depositor); }:_(Origin::Signed(depositor.clone())) verify { assert_eq!( @@ -58,6 +108,8 @@ frame_benchmarking::benchmarks! { withdraw_unbonded_other {}: {} create { + clear_storage::(); + let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); let depositor: T::AccountId = account("depositor", USER_SEED, 0); @@ -67,6 +119,8 @@ frame_benchmarking::benchmarks! { // Make sure no pools exist as a pre-condition for our verify checks assert_eq!(RewardPools::::count(), 0); assert_eq!(BondedPools::::count(), 0); + + whitelist_account!(depositor); }: _( Origin::Signed(depositor.clone()), min_create_bond, @@ -97,6 +151,8 @@ frame_benchmarking::benchmarks! { } nominate { + clear_storage::(); + let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); let depositor = account("depositor", USER_SEED, 0); @@ -121,6 +177,8 @@ frame_benchmarking::benchmarks! { T::Lookup::unlookup(account("stash", USER_SEED, i)) ) .collect(); + + whitelist_account!(depositor); }:_(Origin::Signed(depositor.clone()), pool_account, validators) verify { assert_eq!(RewardPools::::count(), 1); diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index bdfe55c1fbab4..f88ef8f4e506d 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -245,7 +245,6 @@ fn post_checks() { assert!(Delegators::::count() >= BondedPools::::count()); } -#[cfg(test)] pub(crate) fn unsafe_set_state(pool_account: &AccountId, state: PoolState) -> Result<(), ()> { BondedPools::::try_mutate(pool_account, |maybe_bonded_pool| { maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { @@ -254,6 +253,13 @@ pub(crate) fn unsafe_set_state(pool_account: &AccountId, state: PoolState) -> Re }) } +pub(crate) fn clear_storage() { + BondedPools::::remove_all(); + RewardPools::::remove_all(); + SubPoolsStorage::::remove_all(); + Delegators::::remove_all(); +} + #[cfg(test)] mod test { use super::*; From 72fc7af36ad3b076e389efdb7c4da0015e0bc990 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 15 Feb 2022 17:12:48 -0800 Subject: [PATCH 102/299] Benchmark unbond_other --- Cargo.lock | 1 - frame/nomination-pools/Cargo.toml | 15 +- frame/nomination-pools/src/benchmarking.rs | 184 ++++++++++++++++++++- frame/nomination-pools/src/mock.rs | 18 +- frame/staking/src/pallet/impls.rs | 5 + primitives/staking/src/lib.rs | 2 +- 6 files changed, 203 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffb2439321bb3..9bedae2fed3cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6047,7 +6047,6 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 547f7e2f13449..8b68e92faaf0e 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -19,7 +19,6 @@ scale-info = { version = "1.0", default-features = false, features = ["derive"] # FRAME frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-arithmetic = { version = "4.0.0", default-features = false, path = "../../primitives/arithmetic" } sp-runtime = { version = "5.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } @@ -30,25 +29,23 @@ log = { version = "0.4.14", default-features = false } # Optional imports for benchmarking frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking", optional = true, default-features = false } -pallet-balances = { version = "4.0.0-dev", path = "../balances", optional = true, default-features = false } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-io = { version = "5.0.0", path = "../../primitives/io"} sp-tracing = { version = "4.0.0", path = "../../primitives/tracing" } +sp-io = { version = "5.0.0", path = "../../primitives/io" } + [features] default = ["std"] std = [ "codec/std", + "sp-core/std", + "scale-info/std", + "sp-std/std", "frame-support/std", "frame-system/std", - "sp-arithmetic/std", ] runtime-benchmarks = [ - "frame-benchmarking", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", - "pallet-balances", + "frame-benchmarking" ] diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index cfbd472c0f2db..c0f9b68f7b91a 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -3,12 +3,162 @@ use crate::Pallet as Pools; use frame_benchmarking::{account, whitelist_account}; use frame_support::assert_ok; use frame_system::RawOrigin as Origin; -use crate::mock::clear_storage; const USER_SEED: u32 = 0; +fn clear_storage() { + BondedPools::::remove_all(); + RewardPools::::remove_all(); + SubPoolsStorage::::remove_all(); + Delegators::::remove_all(); +} + +fn create_funded_user_with_balance( + string: &'static str, + n: u32, + balance: BalanceOf, +) -> T::AccountId { + let user = account(string, n, USER_SEED); + let _ = T::Currency::make_free_balance_be(&user, balance); + user +} + +// Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free +// balance. +fn create_pool_account(n: u32, balance: BalanceOf) -> (T::AccountId, T::AccountId) { + let pool_creator: T::AccountId = + create_funded_user_with_balance::("pool_creator", n, balance * 2u32.into()); + Pools::::create( + Origin::Signed(pool_creator.clone()).into(), + balance, + n as u16, + pool_creator.clone(), + pool_creator.clone(), + pool_creator.clone(), + ) + .unwrap(); + + let (pool_account, _) = BondedPools::::iter() + .find(|(pool_account, bonded_pool)| bonded_pool.depositor == pool_creator) + .expect("pool_creator created a pool above"); + + (pool_creator, pool_account) +} + +struct ListScenario { + /// Stash/Controller that is expected to be moved. + origin1: T::AccountId, + dest_weight: BalanceOf, + origin1_delegator: Option, +} + +impl ListScenario { + /// An expensive scenario for bags-list implementation: + /// + /// - the node to be updated (r) is the head of a bag that has at least one other node. The bag + /// itself will need to be read and written to update its head. The node pointed to by r.next + /// will need to be read and written as it will need to have its prev pointer updated. Note + /// that there are two other worst case scenarios for bag removal: 1) the node is a tail and + /// 2) the node is a middle node with prev and next; all scenarios end up with the same number + /// of storage reads and writes. + /// + /// - the destination bag has at least one node, which will need its next pointer updated. + /// + /// NOTE: while this scenario specifically targets a worst case for the bags-list, it should + /// also elicit a worst case for other known `SortedListProvider` implementations; although + /// this may not be true against unknown `SortedListProvider` implementations. + pub(crate) fn new( + origin_weight: BalanceOf, + is_increase: bool, + ) -> Result { + ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); + + if MaxPools::::get().unwrap_or(0) < 3 { + // TODO: this is due to mock config, probably best to change + MaxPools::::put(3); + } + + // Burn the entire issuance. + // TODO: probably don't need this + let i = T::Currency::burn(T::Currency::total_issuance()); + sp_std::mem::forget(i); + + let min_create_bond = MinCreateBond::::get(); + println!("min_create_bond {:?}", min_create_bond); + println!("origin weight {:?}", origin_weight); + + // create accounts with the origin weight + let (_, pool_origin1) = create_pool_account::(USER_SEED + 2, origin_weight); + T::StakingInterface::nominate( + pool_origin1.clone(), + // NOTE: these don't really need to be validators. + vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], + )?; + + let (_, pool_origin2) = create_pool_account::(USER_SEED + 3, origin_weight); + T::StakingInterface::nominate( + pool_origin2, + vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))].clone(), + )?; + + // find a destination weight that will trigger the worst case scenario + let dest_weight_as_vote = + T::StakingInterface::weight_update_worst_case(&pool_origin1, is_increase); + + let dest_weight: BalanceOf = + dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; + + // create an account with the worst case destination weight + let (_, pool_dest1) = create_pool_account::(USER_SEED + 1, dest_weight); + T::StakingInterface::nominate( + pool_dest1, + vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], + )?; + + Ok(ListScenario { origin1: pool_origin1, dest_weight, origin1_delegator: None }) + } + + // TODO ADD AMOUNT TO JOIN + fn add_joiner(mut self, amount: BalanceOf) -> Self { + let amount = MinJoinBond::::get() + .max(T::Currency::minimum_balance()) + // max the `given` amount with minimum thresholds for account balance and joining a pool + // to ensure 1. the user can be created and 2. can join the pool + .max(amount); + + let joiner: T::AccountId = account("joiner", USER_SEED, 0); + self.origin1_delegator = Some(joiner.clone()); + T::Currency::make_free_balance_be(&joiner, amount * 2u32.into()); + + let current_bonded = T::StakingInterface::bonded_balance(&self.origin1).unwrap(); + + // Unbond `amount` from the underlying pool account so when the delegator joins + // we will maintain `current_bonded` + T::StakingInterface::unbond(self.origin1.clone(), amount).unwrap(); + + // Account pool points for the unbonded balance + BondedPools::::mutate(&self.origin1, |maybe_pool| + maybe_pool.as_mut().map(|pool| pool.points -= amount) + ); + + Pools::::join( + Origin::Signed(joiner.clone()).into(), + amount, + self.origin1.clone() + ) + .unwrap(); + + assert_eq!( + T::StakingInterface::bonded_balance(&self.origin1).unwrap(), current_bonded + ); + + self + } +} + frame_benchmarking::benchmarks! { join { + // TODO this needs to be bond_extra worst case clear_storage::(); let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); @@ -103,8 +253,36 @@ frame_benchmarking::benchmarks! { ); } - unbond_other {}: {} - pool_withdraw_unbonded {}: {} + unbond_other { + clear_storage::(); + // let depositor = account("depositor", USER_SEED, 0); + + // the weight the nominator will start at. The value used here is expected to be + // significantly higher than the first position in a list (e.g. the first bag threshold). + let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) + .map_err(|_| "balance expected to be a u128") + .unwrap(); + let scenario = ListScenario::::new(origin_weight, false)?; + + let amount = origin_weight - scenario.dest_weight.clone(); + + let scenario = scenario.add_joiner(amount); + + let delegator = scenario.origin1_delegator.unwrap().clone(); + }: _(Origin::Signed(delegator.clone()), delegator.clone()) + verify { + assert!( + T::StakingInterface::bonded_balance(&scenario.origin1).unwrap() + <= scenario.dest_weight.clone() + ); + } + + pool_withdraw_unbonded { + + }: { + + } + withdraw_unbonded_other {}: {} create { diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index f88ef8f4e506d..5bdd304466eee 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -85,6 +85,15 @@ impl sp_staking::StakingInterface for StakingMock { fn max_nominations() -> u32 { 3 } + + fn weight_update_worst_case(who: &Self::AccountId, is_increase: bool) -> u64 { + if is_increase { + u64::MAX / 2 + } else { + MinCreateBond::::get() + .max(StakingMock::minimum_bond()).try_into().unwrap() + } + } } impl frame_system::Config for Runtime { @@ -185,7 +194,7 @@ impl ExtBuilder { } pub(crate) fn build(self) -> sp_io::TestExternalities { - sp_tracing::try_init_simple(); + // sp_tracing::try_init_simple(); let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); @@ -253,13 +262,6 @@ pub(crate) fn unsafe_set_state(pool_account: &AccountId, state: PoolState) -> Re }) } -pub(crate) fn clear_storage() { - BondedPools::::remove_all(); - RewardPools::::remove_all(); - SubPoolsStorage::::remove_all(); - Delegators::::remove_all(); -} - #[cfg(test)] mod test { use super::*; diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 50bdf85e99f2e..d7e75b4a919e9 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1379,4 +1379,9 @@ impl StakingInterface for Pallet { fn max_nominations() -> u32 { T::MaxNominations::get() } + + #[cfg(feature = "runtime-benchmarks")] + fn weight_update_worst_case(who: &Self::AccountId, is_increase: bool) -> u64 { + T::SortedListProvider::weight_update_worst_case(who, is_increase) + } } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 768a3a3a14196..f1e6e5279cc08 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -127,5 +127,5 @@ pub trait StakingInterface { fn max_nominations() -> u32; - // fn setup_nominate_scenario() -> (Self::AccountId, Self::AccountId, Vec); + fn weight_update_worst_case(who: &Self::AccountId, is_increase: bool) -> u64; } From 0298ddc2afe1013ba314403381af987fd71023e5 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 15 Feb 2022 17:43:55 -0800 Subject: [PATCH 103/299] Refactor join benchmark to use scenario setup --- frame/nomination-pools/src/benchmarking.rs | 117 +++++++++------------ frame/nomination-pools/src/mock.rs | 4 +- 2 files changed, 53 insertions(+), 68 deletions(-) diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index c0f9b68f7b91a..2ce5cc4f89080 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -28,6 +28,9 @@ fn create_funded_user_with_balance( fn create_pool_account(n: u32, balance: BalanceOf) -> (T::AccountId, T::AccountId) { let pool_creator: T::AccountId = create_funded_user_with_balance::("pool_creator", n, balance * 2u32.into()); + + println!("ZZZZ min_create_bond {:?}", MinCreateBond::::get()); + println!("ZZZZ origin weight balance {:?}", balance); Pools::::create( Origin::Signed(pool_creator.clone()).into(), balance, @@ -105,6 +108,9 @@ impl ListScenario { let dest_weight_as_vote = T::StakingInterface::weight_update_worst_case(&pool_origin1, is_increase); + println!("is_increase {:?}", is_increase); + println!("dest_weight_as_vote {:?}", dest_weight_as_vote); + let dest_weight: BalanceOf = dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; @@ -137,20 +143,14 @@ impl ListScenario { T::StakingInterface::unbond(self.origin1.clone(), amount).unwrap(); // Account pool points for the unbonded balance - BondedPools::::mutate(&self.origin1, |maybe_pool| + BondedPools::::mutate(&self.origin1, |maybe_pool| { maybe_pool.as_mut().map(|pool| pool.points -= amount) - ); + }); - Pools::::join( - Origin::Signed(joiner.clone()).into(), - amount, - self.origin1.clone() - ) - .unwrap(); + Pools::::join(Origin::Signed(joiner.clone()).into(), amount, self.origin1.clone()) + .unwrap(); - assert_eq!( - T::StakingInterface::bonded_balance(&self.origin1).unwrap(), current_bonded - ); + assert_eq!(T::StakingInterface::bonded_balance(&self.origin1).unwrap(), current_bonded); self } @@ -161,46 +161,29 @@ frame_benchmarking::benchmarks! { // TODO this needs to be bond_extra worst case clear_storage::(); - let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); - let depositor = account("depositor", USER_SEED, 0); + let origin_weight = MinCreateBond::::get().max(T::Currency::minimum_balance()) * 2u32.into(); - // Create a pool - T::Currency::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); - assert_ok!( - Pools::::create( - Origin::Signed(depositor.clone()).into(), - min_create_bond, - 0, - depositor.clone(), - depositor.clone(), - depositor.clone() - ) + // setup the worst case list scenario. + + // the weight the nominator will start at. + let scenario = ListScenario::::new(origin_weight, true)?; + assert_eq!( + T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), + origin_weight ); - // index 0 of the tuple is the key, the pool account - let pool_account = BondedPools::::iter().next().unwrap().0; - // Create the account that will join the pool - let joiner = account("joiner", USER_SEED, 0); - let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); - // and give it some funds. - T::Currency::make_free_balance_be(&joiner, min_join_bond * 2u32.into()); + let max_additional = scenario.dest_weight.clone() - origin_weight; + + let joiner: T::AccountId + = create_funded_user_with_balance::("joiner", 0, max_additional * 2u32.into()); whitelist_account!(joiner); - }: _(Origin::Signed(joiner.clone()), min_join_bond, pool_account.clone()) + }: _(Origin::Signed(joiner.clone()), max_additional, scenario.origin1.clone()) verify { - assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); - assert_eq!(T::StakingInterface::bonded_balance(&pool_account), Some(min_join_bond + min_create_bond)); + assert_eq!(T::Currency::free_balance(&joiner), max_additional); assert_eq!( - BondedPool::::get(&pool_account).unwrap(), - BondedPool { - account: pool_account, - points: min_join_bond + min_create_bond, - depositor: depositor.clone(), - root: depositor.clone(), - nominator: depositor.clone(), - state_toggler: depositor.clone(), - state: PoolState::Open, - } + T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), + scenario.dest_weight ); } @@ -253,36 +236,36 @@ frame_benchmarking::benchmarks! { ); } - unbond_other { - clear_storage::(); - // let depositor = account("depositor", USER_SEED, 0); + // unbond_other { + // clear_storage::(); + // // let depositor = account("depositor", USER_SEED, 0); - // the weight the nominator will start at. The value used here is expected to be - // significantly higher than the first position in a list (e.g. the first bag threshold). - let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) - .map_err(|_| "balance expected to be a u128") - .unwrap(); - let scenario = ListScenario::::new(origin_weight, false)?; + // // the weight the nominator will start at. The value used here is expected to be + // // significantly higher than the first position in a list (e.g. the first bag threshold). + // let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) + // .map_err(|_| "balance expected to be a u128") + // .unwrap(); + // let scenario = ListScenario::::new(origin_weight, false)?; - let amount = origin_weight - scenario.dest_weight.clone(); + // let amount = origin_weight - scenario.dest_weight.clone(); - let scenario = scenario.add_joiner(amount); + // let scenario = scenario.add_joiner(amount); - let delegator = scenario.origin1_delegator.unwrap().clone(); - }: _(Origin::Signed(delegator.clone()), delegator.clone()) - verify { - assert!( - T::StakingInterface::bonded_balance(&scenario.origin1).unwrap() - <= scenario.dest_weight.clone() - ); - } + // let delegator = scenario.origin1_delegator.unwrap().clone(); + // }: _(Origin::Signed(delegator.clone()), delegator.clone()) + // verify { + // assert!( + // T::StakingInterface::bonded_balance(&scenario.origin1).unwrap() + // <= scenario.dest_weight.clone() + // ); + // } - pool_withdraw_unbonded { + // pool_withdraw_unbonded { - }: { + // }: { + + // } - } - withdraw_unbonded_other {}: {} create { diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 5bdd304466eee..0e6123968d8e2 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -91,7 +91,9 @@ impl sp_staking::StakingInterface for StakingMock { u64::MAX / 2 } else { MinCreateBond::::get() - .max(StakingMock::minimum_bond()).try_into().unwrap() + .max(StakingMock::minimum_bond()) + .try_into() + .unwrap() } } } From 87bb2775f03ccf41c0bdcabc32a4fabd3a5dd4c4 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 15 Feb 2022 17:47:35 -0800 Subject: [PATCH 104/299] Clean up and address warnings --- frame/nomination-pools/src/benchmarking.rs | 55 +++++++++------------- frame/nomination-pools/src/mock.rs | 2 +- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index 2ce5cc4f89080..0b2bfdaf169ba 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -29,8 +29,6 @@ fn create_pool_account(n: u32, balance: BalanceOf) -> (T::AccountI let pool_creator: T::AccountId = create_funded_user_with_balance::("pool_creator", n, balance * 2u32.into()); - println!("ZZZZ min_create_bond {:?}", MinCreateBond::::get()); - println!("ZZZZ origin weight balance {:?}", balance); Pools::::create( Origin::Signed(pool_creator.clone()).into(), balance, @@ -42,7 +40,7 @@ fn create_pool_account(n: u32, balance: BalanceOf) -> (T::AccountI .unwrap(); let (pool_account, _) = BondedPools::::iter() - .find(|(pool_account, bonded_pool)| bonded_pool.depositor == pool_creator) + .find(|(_, bonded_pool)| bonded_pool.depositor == pool_creator) .expect("pool_creator created a pool above"); (pool_creator, pool_account) @@ -86,10 +84,6 @@ impl ListScenario { let i = T::Currency::burn(T::Currency::total_issuance()); sp_std::mem::forget(i); - let min_create_bond = MinCreateBond::::get(); - println!("min_create_bond {:?}", min_create_bond); - println!("origin weight {:?}", origin_weight); - // create accounts with the origin weight let (_, pool_origin1) = create_pool_account::(USER_SEED + 2, origin_weight); T::StakingInterface::nominate( @@ -108,9 +102,6 @@ impl ListScenario { let dest_weight_as_vote = T::StakingInterface::weight_update_worst_case(&pool_origin1, is_increase); - println!("is_increase {:?}", is_increase); - println!("dest_weight_as_vote {:?}", dest_weight_as_vote); - let dest_weight: BalanceOf = dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; @@ -236,35 +227,35 @@ frame_benchmarking::benchmarks! { ); } - // unbond_other { - // clear_storage::(); - // // let depositor = account("depositor", USER_SEED, 0); + unbond_other { + clear_storage::(); + // let depositor = account("depositor", USER_SEED, 0); - // // the weight the nominator will start at. The value used here is expected to be - // // significantly higher than the first position in a list (e.g. the first bag threshold). - // let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) - // .map_err(|_| "balance expected to be a u128") - // .unwrap(); - // let scenario = ListScenario::::new(origin_weight, false)?; + // the weight the nominator will start at. The value used here is expected to be + // significantly higher than the first position in a list (e.g. the first bag threshold). + let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) + .map_err(|_| "balance expected to be a u128") + .unwrap(); + let scenario = ListScenario::::new(origin_weight, false)?; - // let amount = origin_weight - scenario.dest_weight.clone(); + let amount = origin_weight - scenario.dest_weight.clone(); - // let scenario = scenario.add_joiner(amount); + let scenario = scenario.add_joiner(amount); - // let delegator = scenario.origin1_delegator.unwrap().clone(); - // }: _(Origin::Signed(delegator.clone()), delegator.clone()) - // verify { - // assert!( - // T::StakingInterface::bonded_balance(&scenario.origin1).unwrap() - // <= scenario.dest_weight.clone() - // ); - // } + let delegator = scenario.origin1_delegator.unwrap().clone(); + }: _(Origin::Signed(delegator.clone()), delegator.clone()) + verify { + assert!( + T::StakingInterface::bonded_balance(&scenario.origin1).unwrap() + <= scenario.dest_weight.clone() + ); + } - // pool_withdraw_unbonded { + pool_withdraw_unbonded { - // }: { + }: { - // } + } withdraw_unbonded_other {}: {} diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 0e6123968d8e2..bd366b04be8f9 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -86,7 +86,7 @@ impl sp_staking::StakingInterface for StakingMock { 3 } - fn weight_update_worst_case(who: &Self::AccountId, is_increase: bool) -> u64 { + fn weight_update_worst_case(_: &Self::AccountId, is_increase: bool) -> u64 { if is_increase { u64::MAX / 2 } else { From f6a438b981bfabf75d39b849659eb154154ca3bb Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 15 Feb 2022 18:59:54 -0800 Subject: [PATCH 105/299] Basic withdraw unbonded benchmarks --- frame/nomination-pools/src/benchmarking.rs | 85 +++++++++++++++++++++- frame/nomination-pools/src/lib.rs | 1 + frame/nomination-pools/src/mock.rs | 4 + primitives/staking/src/lib.rs | 2 + 4 files changed, 88 insertions(+), 4 deletions(-) diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index 0b2bfdaf169ba..87a9d23210f6e 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -115,7 +115,6 @@ impl ListScenario { Ok(ListScenario { origin1: pool_origin1, dest_weight, origin1_delegator: None }) } - // TODO ADD AMOUNT TO JOIN fn add_joiner(mut self, amount: BalanceOf) -> Self { let amount = MinJoinBond::::get() .max(T::Currency::minimum_balance()) @@ -149,7 +148,6 @@ impl ListScenario { frame_benchmarking::benchmarks! { join { - // TODO this needs to be bond_extra worst case clear_storage::(); let origin_weight = MinCreateBond::::get().max(T::Currency::minimum_balance()) * 2u32.into(); @@ -251,13 +249,92 @@ frame_benchmarking::benchmarks! { ); } + // TODO: setup a withdraw unbonded kill scenario pool_withdraw_unbonded { + clear_storage::(); + + let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + + // Add a new delegator + let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); + let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); + Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) + .unwrap(); + + // Sanity check join worked + assert_eq!( + T::StakingInterface::bonded_balance(&pool_account).unwrap( + + ), + min_create_bond + min_join_bond + ); + assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); - }: { + // Unbond the new delegator + Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + // Sanity check that unbond worked + assert_eq!( + T::StakingInterface::bonded_balance(&pool_account).unwrap(), + min_create_bond + ); + // Set the current era + T::StakingInterface::set_current_era(EraIndex::max_value()); + + whitelist_account!(pool_account); + }: _(Origin::Signed(pool_account.clone()), pool_account.clone(), 0) + verify { + // The joiners funds didn't change + assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); + + // TODO: figure out if we can check anything else. Its tricky because the free balance hasn't + // changed and I don't we don't have an api from here to the unlocking chunks, or staking balance lock } - withdraw_unbonded_other {}: {} + // TODO: setup a withdraw unbonded kill scenario, make variable over slashing spans + withdraw_unbonded_other { + clear_storage::(); + + let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + + // Add a new delegator + let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); + let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); + Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) + .unwrap(); + + // Sanity check join worked + assert_eq!( + T::StakingInterface::bonded_balance(&pool_account).unwrap( + + ), + min_create_bond + min_join_bond + ); + assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); + + // Unbond the new delegator + T::StakingInterface::set_current_era(0); + Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + + // Sanity check that unbond worked + assert_eq!( + T::StakingInterface::bonded_balance(&pool_account).unwrap(), + min_create_bond + ); + + // Set the current era to ensure we can withdraw unbonded funds + T::StakingInterface::set_current_era(EraIndex::max_value()); + + whitelist_account!(joiner); + }: _(Origin::Signed(joiner.clone()), joiner.clone(), 0) + verify { + assert_eq!( + T::Currency::free_balance(&joiner), + min_join_bond * 2u32.into() + ); + } create { clear_storage::(); diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 1a605bfa5410b..01bcf70c3a4d8 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -261,6 +261,7 @@ // - backport making ledger generic over ^^ IDEA: maybe staking can slash unlocking chunks, and then // pools is passed the updated unlocking chunks and makes updates based on that // - benchmarks +// - make staking interface current era #![cfg_attr(not(feature = "std"), no_std)] diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index bd366b04be8f9..c297c18ed0d10 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -96,6 +96,10 @@ impl sp_staking::StakingInterface for StakingMock { .unwrap() } } + + fn set_current_era(era: EraIndex) { + CurrentEra::set(era); + } } impl frame_system::Config for Runtime { diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index f1e6e5279cc08..174e8542d9d28 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -128,4 +128,6 @@ pub trait StakingInterface { fn max_nominations() -> u32; fn weight_update_worst_case(who: &Self::AccountId, is_increase: bool) -> u64; + + fn set_current_era(era: EraIndex); } From 706566444bc99794beb8f5709e8749bd99f6ff1c Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 15 Feb 2022 19:04:01 -0800 Subject: [PATCH 106/299] Refactor nominate benchmark --- frame/nomination-pools/src/benchmarking.rs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index 87a9d23210f6e..57790e92cc106 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -382,23 +382,9 @@ frame_benchmarking::benchmarks! { nominate { clear_storage::(); - let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); - let depositor = account("depositor", USER_SEED, 0); - // Create a pool - T::Currency::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); - assert_ok!( - Pools::::create( - Origin::Signed(depositor.clone()).into(), - min_create_bond, - 0, - depositor.clone(), - depositor.clone(), - depositor.clone() - ) - ); - // index 0 of the tuple is the key, the pool account - let pool_account = BondedPools::::iter().next().unwrap().0; + let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); + 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 actual validators let validators: Vec<_> = (0..T::StakingInterface::max_nominations()) From d817c7d9182c019999fe7446c2722a41091de1b4 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 15 Feb 2022 19:05:08 -0800 Subject: [PATCH 107/299] Refactor claim payout --- frame/nomination-pools/src/benchmarking.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index 57790e92cc106..f173c50d9ee78 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -180,22 +180,7 @@ frame_benchmarking::benchmarks! { clear_storage::(); let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); - let depositor = account("depositor", USER_SEED, 0); - - // Create a pool - T::Currency::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); - assert_ok!( - Pools::::create( - Origin::Signed(depositor.clone()).into(), - min_create_bond, - 0, - depositor.clone(), - depositor.clone(), - depositor.clone() - ) - ); - // index 0 of the tuple is the key, the pool account - let pool_account = BondedPools::::iter().next().unwrap().0; + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); let reward_account = RewardPools::::get( pool_account From ae07079958c3692947d3fa176a9bbb1f1c972ddd Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 15 Feb 2022 19:15:59 -0800 Subject: [PATCH 108/299] Add feature sp-staking/runtime-benchmarks --- frame/nomination-pools/Cargo.toml | 3 ++- frame/nomination-pools/src/benchmarking.rs | 1 - frame/nomination-pools/src/lib.rs | 2 ++ frame/staking/Cargo.toml | 1 + frame/staking/src/pallet/impls.rs | 5 +++++ frame/staking/src/tests.rs | 4 ++-- primitives/staking/Cargo.toml | 1 + primitives/staking/src/lib.rs | 3 +++ 8 files changed, 16 insertions(+), 4 deletions(-) diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 8b68e92faaf0e..6d47ad0334b5e 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -47,5 +47,6 @@ std = [ "frame-system/std", ] runtime-benchmarks = [ - "frame-benchmarking" + "frame-benchmarking", + "sp-staking/runtime-benchmarks" ] diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index f173c50d9ee78..86e2d0e5b379b 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -1,7 +1,6 @@ use super::*; use crate::Pallet as Pools; use frame_benchmarking::{account, whitelist_account}; -use frame_support::assert_ok; use frame_system::RawOrigin as Origin; const USER_SEED: u32 = 0; diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 01bcf70c3a4d8..4318ee9db5e27 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -262,6 +262,8 @@ // pools is passed the updated unlocking chunks and makes updates based on that // - benchmarks // - make staking interface current era +// - staking provider current era should not return option, just era index +// - write detailed docs for StakingInterface #![cfg_attr(not(feature = "std"), no_std)] diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index 363ffe4428fb4..b1a0901f65487 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -71,5 +71,6 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-election-provider-support/runtime-benchmarks", "rand_chacha", + "sp-staking/runtime-benchmarks" ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index d7e75b4a919e9..ed4957f0fe89e 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1384,4 +1384,9 @@ impl StakingInterface for Pallet { fn weight_update_worst_case(who: &Self::AccountId, is_increase: bool) -> u64 { T::SortedListProvider::weight_update_worst_case(who, is_increase) } + + #[cfg(feature = "runtime-benchmarks")] + fn set_current_era(era: EraIndex) { + CurrentEra::::put(era) + } } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 87450636f0b2c..4e868acbb90d7 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4713,8 +4713,8 @@ mod sorted_list_provider { } mod staking_interface { - use super::*; - use sp_staking::StakingInterface as _; + // use super::*; + // use sp_staking::StakingInterface as _; // TODO: probably should test all other fns of the interface impl? Although benchmarks should // at least make sure those work on the happy path diff --git a/primitives/staking/Cargo.toml b/primitives/staking/Cargo.toml index 54e556dfae122..3b658515e9341 100644 --- a/primitives/staking/Cargo.toml +++ b/primitives/staking/Cargo.toml @@ -26,3 +26,4 @@ std = [ "sp-runtime/std", "sp-std/std", ] +runtime-benchmarks = [] \ No newline at end of file diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 174e8542d9d28..1a52918b5b701 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -125,9 +125,12 @@ pub trait StakingInterface { // Benchmarking helpers + #[cfg(feature = "runtime-benchmarks")] fn max_nominations() -> u32; + #[cfg(feature = "runtime-benchmarks")] fn weight_update_worst_case(who: &Self::AccountId, is_increase: bool) -> u64; + #[cfg(feature = "runtime-benchmarks")] fn set_current_era(era: EraIndex); } From da82bbcbfb0a9e3920f0e307d3338e80504c833b Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 15 Feb 2022 20:46:02 -0800 Subject: [PATCH 109/299] Get node runtime to compile --- Cargo.lock | 1 + bin/node/runtime/Cargo.toml | 3 +++ bin/node/runtime/src/lib.rs | 31 +++++++++++++++++++++++++++++++ frame/nomination-pools/Cargo.toml | 2 ++ frame/nomination-pools/src/lib.rs | 11 ++++++----- primitives/staking/src/lib.rs | 5 ++++- 6 files changed, 47 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9bedae2fed3cf..01b3961e87524 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4884,6 +4884,7 @@ dependencies = [ "pallet-membership", "pallet-mmr", "pallet-multisig", + "pallet-nomination-pools", "pallet-offences", "pallet-offences-benchmarking", "pallet-preimage", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 2aad40b4f121d..1918a4328f5ed 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -77,6 +77,7 @@ pallet-lottery = { version = "4.0.0-dev", default-features = false, path = "../. pallet-membership = { version = "4.0.0-dev", default-features = false, path = "../../../frame/membership" } pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" } pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } +pallet-nomination-pools = { version = "0.0.1", default-features = false, path = "../../../frame/nomination-pools"} pallet-offences = { version = "4.0.0-dev", default-features = false, path = "../../../frame/offences" } pallet-offences-benchmarking = { version = "4.0.0-dev", path = "../../../frame/offences/benchmarking", default-features = false, optional = true } pallet-preimage = { version = "4.0.0-dev", default-features = false, path = "../../../frame/preimage" } @@ -138,6 +139,7 @@ std = [ "pallet-membership/std", "pallet-mmr/std", "pallet-multisig/std", + "pallet-nomination-pools/std", "pallet-identity/std", "pallet-scheduler/std", "node-primitives/std", @@ -207,6 +209,7 @@ runtime-benchmarks = [ "pallet-membership/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", + "pallet-nomination-pools/runtime-benchmarks", "pallet-offences-benchmarking", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0a13b795919c0..8041804f12b3d 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -562,6 +562,7 @@ impl pallet_staking::Config for Runtime { // Alternatively, use pallet_staking::UseNominatorsMap to just use the nominators map. // Note that the aforementioned does not scale to a very large number of nominators. type SortedListProvider = BagsList; + type PoolsInterface = pallet_nomination_pools::Pallet; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; } @@ -698,6 +699,34 @@ impl pallet_bags_list::Config for Runtime { type BagThresholds = BagThresholds; } +parameter_types! { + pub const PostUnbondPoolsWindow: u32 = 4; +} + +use sp_runtime::traits::Convert; +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(balance: Balance) -> sp_core::U256 { + sp_core::U256::from(balance) + } +} +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { + n.try_into().unwrap_or(Balance::max_value()) + } +} + +impl pallet_nomination_pools::Config for Runtime { + type WeightInfo = (); + type Event = Event; + type Currency = Balances; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type StakingInterface = pallet_staking::Pallet; + type PostUnbondingPoolsWindow = PostUnbondPoolsWindow; +} + parameter_types! { pub const VoteLockingPeriod: BlockNumber = 30 * DAYS; } @@ -1415,6 +1444,7 @@ construct_runtime!( ChildBounties: pallet_child_bounties, Referenda: pallet_referenda, ConvictionVoting: pallet_conviction_voting, + NominationPools: pallet_nomination_pools, } ); @@ -1498,6 +1528,7 @@ mod benches { [pallet_membership, TechnicalMembership] [pallet_mmr, Mmr] [pallet_multisig, Multisig] + [pallet_nomination_pools, NominationPools] [pallet_offences, OffencesBench::] [pallet_preimage, Preimage] [pallet_proxy, Proxy] diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 6d47ad0334b5e..4b5d3fb289523 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -23,6 +23,7 @@ sp-runtime = { version = "5.0.0", default-features = false, path = "../../primit sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } sp-core = { version = "5.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "5.0.0", default-features = false, path = "../../primitives/io" } # third party log = { version = "0.4.14", default-features = false } @@ -41,6 +42,7 @@ default = ["std"] std = [ "codec/std", "sp-core/std", + "sp-io/std", "scale-info/std", "sp-std/std", "frame-support/std", diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 4318ee9db5e27..437e43f618c5a 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -276,9 +276,10 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_core::U256; +use sp_io::hashing::blake2_256; use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZeroInput, Zero}; use sp_staking::{EraIndex, PoolsInterface, SlashPoolArgs, SlashPoolOut, StakingInterface}; -use sp_std::{collections::btree_map::BTreeMap, ops::Div}; +use sp_std::{collections::btree_map::BTreeMap, ops::Div, vec::Vec}; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; @@ -363,8 +364,8 @@ pub struct Delegator { } /// All of a pool's possible states. -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] -#[cfg_attr(feature = "std", derive(Clone, PartialEq))] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone))] pub enum PoolState { Open = 0, Blocked = 1, @@ -1268,9 +1269,9 @@ impl Pallet { let ext_index = frame_system::Pallet::::extrinsic_index().unwrap_or_default(); let stash_entropy = - (b"pools/stash", index, parent_hash, ext_index).using_encoded(sp_core::blake2_256); + (b"pools/stash", index, parent_hash, ext_index).using_encoded(blake2_256); let reward_entropy = - (b"pools/rewards", index, parent_hash, ext_index).using_encoded(sp_core::blake2_256); + (b"pools/rewards", index, parent_hash, ext_index).using_encoded(blake2_256); ( Decode::decode(&mut TrailingZeroInput::new(stash_entropy.as_ref())) diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 1a52918b5b701..d587b4f881910 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -121,7 +121,10 @@ pub trait StakingInterface { payee: Self::AccountId, ) -> DispatchResult; - fn nominate(stash: Self::AccountId, targets: Vec) -> DispatchResult; + fn nominate( + stash: Self::AccountId, + targets: sp_std::vec::Vec, + ) -> DispatchResult; // Benchmarking helpers From f4a584484baa574ab569c635f887570e326b442f Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 15 Feb 2022 21:36:36 -0800 Subject: [PATCH 110/299] Get node to run --- bin/node/cli/src/chain_spec.rs | 1 + frame/nomination-pools/src/benchmarking.rs | 2 +- frame/nomination-pools/src/lib.rs | 16 +++++++++++++--- frame/nomination-pools/src/weights.rs | 14 +++++++------- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 11516f964903a..9632f42694303 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -363,6 +363,7 @@ pub fn testnet_genesis( gilt: Default::default(), transaction_storage: Default::default(), transaction_payment: Default::default(), + nomination_pools: Default::default(), } } diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index 86e2d0e5b379b..376da369253b3 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -1,6 +1,6 @@ use super::*; use crate::Pallet as Pools; -use frame_benchmarking::{account, whitelist_account}; +use frame_benchmarking::{account, whitelist_account, vec}; use frame_system::RawOrigin as Origin; const USER_SEED: u32 = 0; diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 437e43f618c5a..c34cb669d45f3 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -373,8 +373,8 @@ pub enum PoolState { } /// Pool permissions and state -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] -#[cfg_attr(feature = "std", derive(Clone, PartialEq))] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq)] +#[cfg_attr(feature = "std", derive(Clone))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] struct BondedPoolStorage { @@ -793,13 +793,23 @@ pub mod pallet { CountedStorageMap<_, Twox64Concat, T::AccountId, SubPools>; #[pallet::genesis_config] - #[cfg_attr(feature = "std", derive(DefaultNoBound))] pub struct GenesisConfig { pub min_join_bond: BalanceOf, pub min_create_bond: BalanceOf, pub max_pools: Option, } + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { + min_join_bond: Zero::zero(), + min_create_bond: Zero::zero(), + max_pools: Some(10), + } + } + } + #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index db31e0ddfd367..b6eb14b818028 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -14,24 +14,24 @@ pub trait WeightInfo { // For backwards compatibility and tests impl WeightInfo for () { fn join() -> Weight { - Weight::MAX + 0 } fn claim_payout() -> Weight { - Weight::MAX + 0 } fn unbond_other() -> Weight { - Weight::MAX + 0 } fn pool_withdraw_unbonded() -> Weight { - Weight::MAX + 0 } fn withdraw_unbonded_other() -> Weight { - Weight::MAX + 0 } fn create() -> Weight { - Weight::MAX + 0 } fn nominate() -> Weight { - Weight::MAX + 0 } } From a4b2661fafd3f231b17430fdb29074f3983da61e Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 18 Feb 2022 11:09:28 -0800 Subject: [PATCH 111/299] Make claim_payout bench work with node --- frame/nomination-pools/src/benchmarking.rs | 25 +++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index 376da369253b3..9e766f3e32ab2 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -18,7 +18,7 @@ fn create_funded_user_with_balance( balance: BalanceOf, ) -> T::AccountId { let user = account(string, n, USER_SEED); - let _ = T::Currency::make_free_balance_be(&user, balance); + T::Currency::make_free_balance_be(&user, balance); user } @@ -152,23 +152,23 @@ frame_benchmarking::benchmarks! { let origin_weight = MinCreateBond::::get().max(T::Currency::minimum_balance()) * 2u32.into(); // setup the worst case list scenario. - - // the weight the nominator will start at. let scenario = ListScenario::::new(origin_weight, true)?; assert_eq!( T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), origin_weight ); + let max_additional = scenario.dest_weight.clone() - origin_weight; + let joiner_free = T::Currency::minimum_balance() + max_additional; let joiner: T::AccountId - = create_funded_user_with_balance::("joiner", 0, max_additional * 2u32.into()); + = create_funded_user_with_balance::("joiner", 0, joiner_free); whitelist_account!(joiner); }: _(Origin::Signed(joiner.clone()), max_additional, scenario.origin1.clone()) verify { - assert_eq!(T::Currency::free_balance(&joiner), max_additional); + assert_eq!(T::Currency::free_balance(&joiner), joiner_free - max_additional); assert_eq!( T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), scenario.dest_weight @@ -178,8 +178,8 @@ frame_benchmarking::benchmarks! { claim_payout { clear_storage::(); - let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + let origin_weight = MinCreateBond::::get().max(T::Currency::minimum_balance()) * 2u32.into(); + let (depositor, pool_account) = create_pool_account::(0, origin_weight); let reward_account = RewardPools::::get( pool_account @@ -188,12 +188,12 @@ frame_benchmarking::benchmarks! { .account; // Send funds to the reward account of the pool - T::Currency::make_free_balance_be(&reward_account, min_create_bond); + T::Currency::make_free_balance_be(&reward_account, origin_weight); // Sanity check assert_eq!( T::Currency::free_balance(&depositor), - min_create_bond + origin_weight ); whitelist_account!(depositor); @@ -201,7 +201,7 @@ frame_benchmarking::benchmarks! { verify { assert_eq!( T::Currency::free_balance(&depositor), - min_create_bond * 2u32.into() + origin_weight * 2u32.into() ); assert_eq!( T::Currency::free_balance(&reward_account), @@ -210,6 +210,7 @@ frame_benchmarking::benchmarks! { } unbond_other { + log::info!("unbond_other bench"); clear_storage::(); // let depositor = account("depositor", USER_SEED, 0); @@ -235,6 +236,7 @@ frame_benchmarking::benchmarks! { // TODO: setup a withdraw unbonded kill scenario pool_withdraw_unbonded { + log::info!("pool_withdraw_unbonded bench"); clear_storage::(); let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); @@ -278,6 +280,7 @@ frame_benchmarking::benchmarks! { // TODO: setup a withdraw unbonded kill scenario, make variable over slashing spans withdraw_unbonded_other { + log::info!("withdraw_unbonded_other bench"); clear_storage::(); let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); @@ -321,6 +324,7 @@ frame_benchmarking::benchmarks! { } create { + log::info!("create bench"); clear_storage::(); let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); @@ -364,6 +368,7 @@ frame_benchmarking::benchmarks! { } nominate { + log::info!("nominate bench"); clear_storage::(); // Create a pool From efbc1193b17ce629d6dcfbb17b535263502dc03e Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 18 Feb 2022 11:26:43 -0800 Subject: [PATCH 112/299] Make pool_withdraw_unbonded bench work with node --- frame/nomination-pools/src/benchmarking.rs | 9 ++++----- frame/nomination-pools/src/lib.rs | 6 +----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index 9e766f3e32ab2..7fc57e98ac607 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -1,6 +1,6 @@ use super::*; use crate::Pallet as Pools; -use frame_benchmarking::{account, whitelist_account, vec}; +use frame_benchmarking::{account, vec, whitelist_account}; use frame_system::RawOrigin as Origin; const USER_SEED: u32 = 0; @@ -210,9 +210,7 @@ frame_benchmarking::benchmarks! { } unbond_other { - log::info!("unbond_other bench"); clear_storage::(); - // let depositor = account("depositor", USER_SEED, 0); // 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). @@ -236,10 +234,11 @@ frame_benchmarking::benchmarks! { // TODO: setup a withdraw unbonded kill scenario pool_withdraw_unbonded { - log::info!("pool_withdraw_unbonded bench"); clear_storage::(); - let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(T::Currency::minimum_balance()); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Add a new delegator diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index c34cb669d45f3..005b901bf532e 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -802,11 +802,7 @@ pub mod pallet { #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { - Self { - min_join_bond: Zero::zero(), - min_create_bond: Zero::zero(), - max_pools: Some(10), - } + Self { min_join_bond: Zero::zero(), min_create_bond: Zero::zero(), max_pools: Some(10) } } } From 5ad827281e4bda61886764c2bac93adfe2db4014 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 18 Feb 2022 11:34:42 -0800 Subject: [PATCH 113/299] Make withdraw_unbonded_other work with node runtime' --- frame/nomination-pools/src/benchmarking.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index 7fc57e98ac607..4f8aa5a0affde 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -279,10 +279,11 @@ frame_benchmarking::benchmarks! { // TODO: setup a withdraw unbonded kill scenario, make variable over slashing spans withdraw_unbonded_other { - log::info!("withdraw_unbonded_other bench"); clear_storage::(); - let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(T::Currency::minimum_balance()); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Add a new delegator From 6fce5481bf3d8fc98674f59cc8f0844996e3a440 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 18 Feb 2022 11:41:00 -0800 Subject: [PATCH 114/299] Make create benchmark work with node --- frame/nomination-pools/src/benchmarking.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index 4f8aa5a0affde..4866493109b69 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -324,10 +324,11 @@ frame_benchmarking::benchmarks! { } create { - log::info!("create bench"); clear_storage::(); - let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(T::Currency::minimum_balance()); let depositor: T::AccountId = account("depositor", USER_SEED, 0); // Give the depositor some balance to bond From e25f2215118614229625df476ee258f53ad0b67d Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 18 Feb 2022 11:49:24 -0800 Subject: [PATCH 115/299] Make nominate benchmark work with node runtime --- frame/nomination-pools/src/benchmarking.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index 4866493109b69..cdb70a72abd42 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -369,11 +369,12 @@ frame_benchmarking::benchmarks! { } nominate { - log::info!("nominate bench"); clear_storage::(); // Create a pool - let min_create_bond = MinCreateBond::::get().max(T::StakingInterface::minimum_bond()); + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(T::Currency::minimum_balance()); 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 actual validators From e5915cf3112587803cef86725c9368bf01183ad1 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sat, 19 Feb 2022 12:44:28 -0800 Subject: [PATCH 116/299] WiP new benchmark crate --- .../nomination-pools/benchmarking/Cargo.toml | 33 ++++ frame/nomination-pools/benchmarking/README.md | 0 .../nomination-pools/benchmarking/src/lib.rs | 2 + .../nomination-pools/benchmarking/src/mock.rs | 160 ++++++++++++++++++ frame/nomination-pools/src/benchmarking.rs | 9 +- frame/nomination-pools/src/mock.rs | 1 + frame/nomination-pools/src/weights.rs | 8 +- frame/staking/src/benchmarking.rs | 2 +- frame/staking/src/pallet/impls.rs | 7 + 9 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 frame/nomination-pools/benchmarking/Cargo.toml create mode 100644 frame/nomination-pools/benchmarking/README.md create mode 100644 frame/nomination-pools/benchmarking/src/lib.rs create mode 100644 frame/nomination-pools/benchmarking/src/mock.rs diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml new file mode 100644 index 0000000000000..409ef49bf64ed --- /dev/null +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "pallet-nomination-pools-benchmarking" +version = "4.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME nomination pools pallet benchmarking" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# Parity +codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } +scale-info = { version = "1.0", default-features = false, features = ["derive"] } + +# FRAME +pallet-staking = { version = "4.0.0-dev", default-features = false, features = ["runtime-benchmarks"], path = "../../staking" } +pallet-bags-list = { version = "4.0.0-dev", features = ["runtime-benchmarks"], path = "../../bags-list" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } + +[features] +default = ["std"] +std = [ + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "pallet-staking/std", +] \ No newline at end of file diff --git a/frame/nomination-pools/benchmarking/README.md b/frame/nomination-pools/benchmarking/README.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs new file mode 100644 index 0000000000000..1b95c9c90a8d3 --- /dev/null +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -0,0 +1,2 @@ +#[cfg(test)] +use mock; \ No newline at end of file diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs new file mode 100644 index 0000000000000..6ce7c99153876 --- /dev/null +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -0,0 +1,160 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-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. + +#![cfg(test)] + +use super::*; + +type AccountId = u64; +type AccountIndex = u32; +type BlockNumber = u64; +type Balance = u64; + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(2 * WEIGHT_PER_SECOND); +} + +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>; +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 10; +} +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 = (); +} + + +impl pallet_staking::Config for Runtime { + type MaxNominations = ConstU32<16>; + type Currency = Balances; + 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 = (); + type SessionInterface = Self; + type EraPayout = pallet_staking::ConvertCurve; + type NextNewSession = Session; + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = (); + type ElectionProvider = onchain::OnChainSequentialPhragmen; + type GenesisElectionProvider = Self::ElectionProvider; + type SortedListProvider = pallet_bags_list::Pallet; + 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 bags_list::Config for Runtime { + type Event = Event; + type WeightInfo = (); + type BagThresholds = BagThresholds; + type VoteWeightProvider = pallet_staking::Pallet; +} + + +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub static PostUnbondingPoolsWindow: u32 = 10; +} + +impl pallet_nomination_pools::Config for Runtime { + type Event = Event; + type WeightInfo = (); + type Currency = Balances; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type StakingInterface = StakingMock; + type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; +} + + +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet, Call, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, + Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + sp_io::TestExternalities::new(t) +} \ No newline at end of file diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index cdb70a72abd42..ffbe7376e2fea 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -4,6 +4,7 @@ use frame_benchmarking::{account, vec, whitelist_account}; use frame_system::RawOrigin as Origin; const USER_SEED: u32 = 0; +const MAX_SPANS: u32 = 100; fn clear_storage() { BondedPools::::remove_all(); @@ -234,6 +235,7 @@ frame_benchmarking::benchmarks! { // TODO: setup a withdraw unbonded kill scenario pool_withdraw_unbonded { + let s in 0 .. MAX_SPANS; clear_storage::(); let min_create_bond = MinCreateBond::::get() @@ -267,8 +269,9 @@ frame_benchmarking::benchmarks! { // Set the current era T::StakingInterface::set_current_era(EraIndex::max_value()); + // T::StakingInterface::add_slashing_spans(&pool_account, s); whitelist_account!(pool_account); - }: _(Origin::Signed(pool_account.clone()), pool_account.clone(), 0) + }: _(Origin::Signed(pool_account.clone()), pool_account.clone(), 2) verify { // The joiners funds didn't change assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); @@ -279,8 +282,11 @@ frame_benchmarking::benchmarks! { // TODO: setup a withdraw unbonded kill scenario, make variable over slashing spans withdraw_unbonded_other { + let s in 0 .. MAX_SPANS; clear_storage::(); + // T::StakingInterface::add_slashing_spans(&stash, s); + let min_create_bond = MinCreateBond::::get() .max(T::StakingInterface::minimum_bond()) .max(T::Currency::minimum_balance()); @@ -314,6 +320,7 @@ frame_benchmarking::benchmarks! { // Set the current era to ensure we can withdraw unbonded funds T::StakingInterface::set_current_era(EraIndex::max_value()); + // T::StakingInterface::add_slashing_spans(&pool_account, s); whitelist_account!(joiner); }: _(Origin::Signed(joiner.clone()), joiner.clone(), 0) verify { diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index c297c18ed0d10..c97879fe2345a 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -100,6 +100,7 @@ impl sp_staking::StakingInterface for StakingMock { fn set_current_era(era: EraIndex) { CurrentEra::set(era); } + } impl frame_system::Config for Runtime { diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index b6eb14b818028..126ab600f3fbf 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -5,8 +5,8 @@ pub trait WeightInfo { fn join() -> Weight; fn claim_payout() -> Weight; fn unbond_other() -> Weight; - fn pool_withdraw_unbonded() -> Weight; - fn withdraw_unbonded_other() -> Weight; + fn pool_withdraw_unbonded(s: u32) -> Weight; + fn withdraw_unbonded_other(s: u32) -> Weight; fn create() -> Weight; fn nominate() -> Weight; } @@ -22,10 +22,10 @@ impl WeightInfo for () { fn unbond_other() -> Weight { 0 } - fn pool_withdraw_unbonded() -> Weight { + fn pool_withdraw_unbonded(_s: u32) -> Weight { 0 } - fn withdraw_unbonded_other() -> Weight { + fn withdraw_unbonded_other(_s: u32) -> Weight { 0 } fn create() -> Weight { diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index f9fd5aed44725..f148d16b44ef9 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -49,7 +49,7 @@ type MaxNominators = <::BenchmarkingConfig as BenchmarkingConfig // Add slashing spans to a user account. Not relevant for actual use, only to benchmark // read and write operations. -fn add_slashing_spans(who: &T::AccountId, spans: u32) { +pub(crate) fn add_slashing_spans(who: &T::AccountId, spans: u32) { if spans == 0 { return } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index ed4957f0fe89e..8e220e8056955 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1389,4 +1389,11 @@ impl StakingInterface for Pallet { fn set_current_era(era: EraIndex) { CurrentEra::::put(era) } + + fn add_slashing_spans(_account: &Self::AccountId, _spans: u32) { + // TODO: this needs a better design. In order to access the slashing_spans storage + // we would need to bound T: pallet_staking::Config - however we want to avoid bounding + // the nomination pools + // benchmarking::add_slashing_span::(account, spans) + } } From f91539030185af3d13e98316d59f87246e827744 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 21 Feb 2022 13:02:37 -0800 Subject: [PATCH 117/299] Implement initial mock for benchmarks --- Cargo.lock | 24 +++++- Cargo.toml | 1 + bin/node/runtime/Cargo.toml | 2 +- frame/election-provider-support/src/lib.rs | 10 ++- frame/nomination-pools/Cargo.toml | 2 +- .../nomination-pools/benchmarking/Cargo.toml | 22 +++++- .../nomination-pools/benchmarking/src/lib.rs | 2 +- .../nomination-pools/benchmarking/src/mock.rs | 77 ++++++++++++------- frame/nomination-pools/src/mock.rs | 1 - frame/staking/src/lib.rs | 14 +++- frame/staking/src/pallet/impls.rs | 7 -- 11 files changed, 118 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c9f11557f5a0..6bda1b4f1d18a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6047,7 +6047,7 @@ dependencies = [ [[package]] name = "pallet-nomination-pools" -version = "0.0.1" +version = "1.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -6064,6 +6064,28 @@ dependencies = [ "sp-tracing", ] +[[package]] +name = "pallet-nomination-pools-benchmarking" +version = "4.0.0" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "pallet-bags-list", + "pallet-balances", + "pallet-nomination-pools", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", +] + [[package]] name = "pallet-offences" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 4c7d1c32ae6c0..255bb65d60e10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,6 +107,7 @@ members = [ "frame/preimage", "frame/proxy", "frame/nomination-pools", + "frame/nomination-pools/benchmarking", "frame/randomness-collective-flip", "frame/recovery", "frame/referenda", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 9d5d28b451392..7269a1b13106a 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -76,7 +76,7 @@ pallet-lottery = { version = "4.0.0-dev", default-features = false, path = "../. pallet-membership = { version = "4.0.0-dev", default-features = false, path = "../../../frame/membership" } pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" } pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } -pallet-nomination-pools = { version = "0.0.1", default-features = false, path = "../../../frame/nomination-pools"} +pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"} pallet-offences = { version = "4.0.0-dev", default-features = false, path = "../../../frame/offences" } pallet-offences-benchmarking = { version = "4.0.0-dev", path = "../../../frame/offences/benchmarking", default-features = false, optional = true } pallet-preimage = { version = "4.0.0-dev", default-features = false, path = "../../../frame/preimage" } diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 26efe5107b670..529af4f5a250e 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -262,6 +262,8 @@ pub trait ElectionDataProvider { fn clear() {} } +// TODO: [now] this isn't practically useful, we require the dataprovider for staking's election +// provider to be the staking pallet /// An election data provider that should only be used for testing. #[cfg(feature = "std")] pub struct TestDataProvider(sp_std::marker::PhantomData); @@ -340,11 +342,15 @@ pub trait InstantElectionProvider: ElectionProvider { pub struct NoElection(sp_std::marker::PhantomData); #[cfg(feature = "std")] -impl ElectionProvider for NoElection<(AccountId, BlockNumber)> { +impl ElectionProvider + for NoElection<(AccountId, BlockNumber, DataProvider)> +where + DataProvider: ElectionDataProvider, +{ type AccountId = AccountId; type BlockNumber = BlockNumber; type Error = &'static str; - type DataProvider = TestDataProvider<(AccountId, BlockNumber)>; + type DataProvider = DataProvider; fn elect() -> Result, Self::Error> { Err(" cannot do anything.") diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 4b5d3fb289523..d3d3a92e4e3c6 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-nomination-pools" -version = "0.0.1" +version = "1.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index 409ef49bf64ed..76d5c5ccde2e6 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -18,10 +18,25 @@ codec = { package = "parity-scale-codec", version = "2.0.0", default-features = scale-info = { version = "1.0", default-features = false, features = ["derive"] } # FRAME -pallet-staking = { version = "4.0.0-dev", default-features = false, features = ["runtime-benchmarks"], path = "../../staking" } -pallet-bags-list = { version = "4.0.0-dev", features = ["runtime-benchmarks"], path = "../../bags-list" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } +frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../election-provider-support" } 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-bags-list = { version = "4.0.0-dev", features = ["runtime-benchmarks"], path = "../../bags-list" } +pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } +pallet-staking = { version = "4.0.0-dev", default-features = false, features = ["runtime-benchmarks"], path = "../../staking" } +pallet-nomination-pools = { version = "1.0.0", default-features = false, features = ["runtime-benchmarks"], path = "../" } + +# Substrate +sp-runtime = { version = "5.0.0", default-features = false, path = "../../../primitives/runtime" } + +[dev-dependencies] +pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } +pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } +pallet-session = { version = "4.0.0-dev", default-features = false, path = "../../session" } +sp-core = { version = "5.0.0", path = "../../../primitives/core" } +sp-io = { version = "5.0.0", path = "../../../primitives/io" } + [features] default = ["std"] @@ -29,5 +44,8 @@ std = [ "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "frame-election-provider-support/std", + "pallet-balances/std", "pallet-staking/std", + "sp-runtime/std" ] \ No newline at end of file diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 1b95c9c90a8d3..87018e348509f 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -1,2 +1,2 @@ #[cfg(test)] -use mock; \ No newline at end of file +mod mock; diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 6ce7c99153876..12f471482ebeb 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -15,20 +15,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg(test)] - -use super::*; +use frame_election_provider_support::VoteWeight; +use frame_support::{pallet_prelude::*, parameter_types, traits::ConstU64}; +use sp_runtime::traits::{Convert, IdentityLookup}; type AccountId = u64; type AccountIndex = u32; type BlockNumber = u64; type Balance = u64; -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(2 * WEIGHT_PER_SECOND); -} - impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -56,6 +51,13 @@ impl frame_system::Config for Runtime { 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 const ExistentialDeposit: Balance = 10; } @@ -71,7 +73,19 @@ impl pallet_balances::Config for Runtime { 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; +} impl pallet_staking::Config for Runtime { type MaxNominations = ConstU32<16>; type Currency = Balances; @@ -85,14 +99,16 @@ impl pallet_staking::Config for Runtime { type SlashDeferDuration = (); type SlashCancelOrigin = frame_system::EnsureRoot; type BondingDuration = (); - type SessionInterface = Self; + type SessionInterface = (); type EraPayout = pallet_staking::ConvertCurve; - type NextNewSession = Session; + type NextNewSession = (); type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = (); - type ElectionProvider = onchain::OnChainSequentialPhragmen; + type ElectionProvider = + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; type GenesisElectionProvider = Self::ElectionProvider; type SortedListProvider = pallet_bags_list::Pallet; + type PoolsInterface = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } @@ -101,24 +117,23 @@ parameter_types! { pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; } -impl bags_list::Config for Runtime { +impl pallet_bags_list::Config for Runtime { type Event = Event; type WeightInfo = (); type BagThresholds = BagThresholds; - type VoteWeightProvider = pallet_staking::Pallet; + type VoteWeightProvider = Staking; } - pub struct BalanceToU256; -impl Convert for BalanceToU256 { - fn convert(n: Balance) -> U256 { +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> sp_core::U256 { n.into() } } pub struct U256ToBalance; -impl Convert for U256ToBalance { - fn convert(n: U256) -> Balance { +impl Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { n.try_into().unwrap() } } @@ -133,28 +148,36 @@ impl pallet_nomination_pools::Config for Runtime { type Currency = Balances; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type StakingInterface = StakingMock; + type StakingInterface = Staking; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; } +impl frame_system::offchain::SendTransactionTypes for Runtime +where + Call: From, +{ + type OverarchingCall = Call; + type Extrinsic = UncheckedExtrinsic; +} -pub type Block = sp_runtime::generic::Block; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; - +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; frame_support::construct_runtime!( - pub enum Test where + pub enum Runtime where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: 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}, } ); pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); sp_io::TestExternalities::new(t) -} \ No newline at end of file +} diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index c97879fe2345a..c297c18ed0d10 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -100,7 +100,6 @@ impl sp_staking::StakingInterface for StakingMock { fn set_current_era(era: EraIndex) { CurrentEra::set(era); } - } impl frame_system::Config for Runtime { diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index b45f6fabbd729..0d7c2387c80e5 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -704,7 +704,7 @@ impl UnappliedSlash { /// Means for interacting with a specialized version of the `session` trait. /// /// This is needed because `Staking` sets the `ValidatorIdOf` of the `pallet_session::Config` -pub trait SessionInterface: frame_system::Config { +pub trait SessionInterface { /// Disable the validator at the given index, returns `false` if the validator was already /// disabled or the index is out of bounds. fn disable_validator(validator_index: u32) -> bool; @@ -741,6 +741,18 @@ where } } +impl SessionInterface for () { + fn disable_validator(_: u32) -> bool { + true + } + fn validators() -> Vec { + Vec::new() + } + fn prune_historical_up_to(_: SessionIndex) { + () + } +} + /// Handler for determining how much of a balance should be paid out on the current era. pub trait EraPayout { /// Determine the payout for this era. diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 8e220e8056955..ed4957f0fe89e 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1389,11 +1389,4 @@ impl StakingInterface for Pallet { fn set_current_era(era: EraIndex) { CurrentEra::::put(era) } - - fn add_slashing_spans(_account: &Self::AccountId, _spans: u32) { - // TODO: this needs a better design. In order to access the slashing_spans storage - // we would need to bound T: pallet_staking::Config - however we want to avoid bounding - // the nomination pools - // benchmarking::add_slashing_span::(account, spans) - } } From f688a91db0bcdad282547983a954142da77e4674 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 21 Feb 2022 14:18:56 -0800 Subject: [PATCH 118/299] Establish benchmark setup logic --- Cargo.lock | 2 +- .../nomination-pools/benchmarking/Cargo.toml | 9 +- .../nomination-pools/benchmarking/src/lib.rs | 441 ++++++++++++++++++ frame/nomination-pools/src/benchmark_utils.rs | 23 + frame/nomination-pools/src/lib.rs | 29 +- 5 files changed, 486 insertions(+), 18 deletions(-) create mode 100644 frame/nomination-pools/src/benchmark_utils.rs diff --git a/Cargo.lock b/Cargo.lock index 6bda1b4f1d18a..91c47504c8d28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6075,7 +6075,6 @@ dependencies = [ "pallet-bags-list", "pallet-balances", "pallet-nomination-pools", - "pallet-session", "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", @@ -6084,6 +6083,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", + "sp-staking", ] [[package]] diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index 76d5c5ccde2e6..549b90f47daf8 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -23,17 +23,17 @@ frame-election-provider-support = { version = "4.0.0-dev", default-features = fa 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-bags-list = { version = "4.0.0-dev", features = ["runtime-benchmarks"], path = "../../bags-list" } -pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } pallet-staking = { version = "4.0.0-dev", default-features = false, features = ["runtime-benchmarks"], path = "../../staking" } pallet-nomination-pools = { version = "1.0.0", default-features = false, features = ["runtime-benchmarks"], path = "../" } -# Substrate +# Substrate Primitives sp-runtime = { version = "5.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } [dev-dependencies] +pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } -pallet-session = { version = "4.0.0-dev", default-features = false, path = "../../session" } sp-core = { version = "5.0.0", path = "../../../primitives/core" } sp-io = { version = "5.0.0", path = "../../../primitives/io" } @@ -47,5 +47,6 @@ std = [ "frame-election-provider-support/std", "pallet-balances/std", "pallet-staking/std", - "sp-runtime/std" + "sp-runtime/std", + "sp-staking/std" ] \ No newline at end of file diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 87018e348509f..f10916d3c72c7 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -1,2 +1,443 @@ #[cfg(test)] mod mock; + +use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelist_account}; +use frame_support::ensure; +use frame_system::RawOrigin as Origin; +use pallet_nomination_pools::{ + benchmark_utils::{find_pool_account_by_depositor, unbond_pool}, + BalanceOf, Pallet as Pools, +}; +use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZeroInput, Zero}; +use sp_staking::StakingInterface; + +const USER_SEED: u32 = 0; +const MAX_SPANS: u32 = 100; + +// pub trait NominationPoolsBenchConfig: +// pallet_nomination_pools::Config + pallet_staking::Config + pallet_bags_list::Config +// { +// } +pub trait Config: + pallet_nomination_pools::Config + pallet_staking::Config + pallet_bags_list::Config +{ +} + +pub struct Pallet(Pools); + +fn clear_storage() { + pallet_nomination_pools::BondedPools::::remove_all(); + pallet_nomination_pools::RewardPools::::remove_all(); + pallet_nomination_pools::SubPoolsStorage::::remove_all(); + pallet_nomination_pools::Delegators::::remove_all(); +} + +// TODO: [now] replace this with staking test helper util +fn create_funded_user_with_balance( + string: &'static str, + n: u32, + balance: BalanceOf, +) -> T::AccountId { + let user = account(string, n, USER_SEED); + T::Currency::make_free_balance_be(&user, balance); + user +} + +// Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free +// balance. +fn create_pool_account( + n: u32, + balance: BalanceOf, +) -> (T::AccountId, T::AccountId) { + let pool_creator: T::AccountId = + create_funded_user_with_balance::("pool_creator", n, balance * 2u32.into()); + + Pools::::create( + Origin::Signed(pool_creator.clone()).into(), + balance, + n as u16, + pool_creator.clone(), + pool_creator.clone(), + pool_creator.clone(), + ) + .unwrap(); + + let pool_account = find_pool_account_by_depositor::(&pool_creator) + .expect("pool_creator created a pool above"); + + (pool_creator, pool_account) +} + +struct ListScenario { + /// Stash/Controller that is expected to be moved. + origin1: T::AccountId, + dest_weight: BalanceOf, + origin1_delegator: Option, +} + +impl ListScenario { + /// An expensive scenario for bags-list implementation: + /// + /// - the node to be updated (r) is the head of a bag that has at least one other node. The bag + /// itself will need to be read and written to update its head. The node pointed to by r.next + /// will need to be read and written as it will need to have its prev pointer updated. Note + /// that there are two other worst case scenarios for bag removal: 1) the node is a tail and + /// 2) the node is a middle node with prev and next; all scenarios end up with the same number + /// of storage reads and writes. + /// + /// - the destination bag has at least one node, which will need its next pointer updated. + /// + /// NOTE: while this scenario specifically targets a worst case for the bags-list, it should + /// also elicit a worst case for other known `SortedListProvider` implementations; although + /// this may not be true against unknown `SortedListProvider` implementations. + pub(crate) fn new( + origin_weight: BalanceOf, + is_increase: bool, + ) -> Result { + ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); + + ensure!( + pallet_nomination_pools::MaxPools::::get().unwrap_or(0) >= 3, + "must allow at least three pools for benchmarks" + ); + + // Burn the entire issuance. + // TODO: probably don't need this + // let i = T::Currency::burn(T::Currency::total_issuance()); + // sp_std::mem::forget(i); + + // create accounts with the origin weight + let (_, pool_origin1) = create_pool_account::(USER_SEED + 2, origin_weight); + T::StakingInterface::nominate( + pool_origin1.clone(), + // NOTE: these don't really need to be validators. + vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], + )?; + + let (_, pool_origin2) = create_pool_account::(USER_SEED + 3, origin_weight); + T::StakingInterface::nominate( + pool_origin2, + vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))].clone(), + )?; + + // find a destination weight that will trigger the worst case scenario + let dest_weight_as_vote = + T::StakingInterface::weight_update_worst_case(&pool_origin1, is_increase); + + let dest_weight: BalanceOf = + dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; + + // create an account with the worst case destination weight + let (_, pool_dest1) = create_pool_account::(USER_SEED + 1, dest_weight); + T::StakingInterface::nominate( + pool_dest1, + vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], + )?; + + Ok(ListScenario { origin1: pool_origin1, dest_weight, origin1_delegator: None }) + } + + fn add_joiner(mut self, amount: BalanceOf) -> Self { + let amount = pallet_nomination_pools::MinJoinBond::::get() + .max(::Currency::minimum_balance()) + // max the `given` amount with minimum thresholds for account balance and joining a pool + // to ensure 1. the user can be created and 2. can join the pool + .max(amount); + + let joiner: T::AccountId = account("joiner", USER_SEED, 0); + self.origin1_delegator = Some(joiner.clone()); + ::Currency::make_free_balance_be( + &joiner, + amount * 2u32.into(), + ); + + let current_bonded = T::StakingInterface::bonded_balance(&self.origin1).unwrap(); + + // Unbond `amount` from the underlying pool account so when the delegator joins + // we will maintain `current_bonded` + unbond_pool::(self.origin1.clone(), amount); + + Pools::::join(Origin::Signed(joiner.clone()).into(), amount, self.origin1.clone()) + .unwrap(); + + assert_eq!(T::StakingInterface::bonded_balance(&self.origin1).unwrap(), current_bonded); + + self + } +} + +// frame_benchmarking::benchmarks! { +// join { +// clear_storage::(); + +// let origin_weight = +// pallet_nomination_pools::MinCreateBond::::get().max(T::Currency::minimum_balance()) * +// 2u32.into(); + +// // setup the worst case list scenario. +// let scenario = ListScenario::::new(origin_weight, true)?; +// assert_eq!( +// T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), +// origin_weight +// ); + +// let max_additional = scenario.dest_weight.clone() - origin_weight; +// let joiner_free = T::Currency::minimum_balance() + max_additional; + +// let joiner: T::AccountId +// = create_funded_user_with_balance::("joiner", 0, joiner_free); + +// whitelist_account!(joiner); +// }: _(Origin::Signed(joiner.clone()), max_additional, scenario.origin1.clone()) +// verify { +// assert_eq!(T::Currency::free_balance(&joiner), joiner_free - max_additional); +// assert_eq!( +// T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), +// scenario.dest_weight +// ); +// } + +// claim_payout { +// clear_storage::(); + +// let origin_weight = MinCreateBond::::get().max(T::Currency::minimum_balance()) * 2u32.into(); +// let (depositor, pool_account) = create_pool_account::(0, origin_weight); + +// let reward_account = RewardPools::::get( +// pool_account +// ) +// .unwrap() +// .account; + +// // Send funds to the reward account of the pool +// T::Currency::make_free_balance_be(&reward_account, origin_weight); + +// // Sanity check +// assert_eq!( +// T::Currency::free_balance(&depositor), +// origin_weight +// ); + +// whitelist_account!(depositor); +// }:_(Origin::Signed(depositor.clone())) +// verify { +// assert_eq!( +// T::Currency::free_balance(&depositor), +// origin_weight * 2u32.into() +// ); +// assert_eq!( +// T::Currency::free_balance(&reward_account), +// Zero::zero() +// ); +// } + +// unbond_other { +// clear_storage::(); + +// // the weight the nominator will start at. The value used here is expected to be +// // significantly higher than the first position in a list (e.g. the first bag threshold). +// let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) +// .map_err(|_| "balance expected to be a u128") +// .unwrap(); +// let scenario = ListScenario::::new(origin_weight, false)?; + +// let amount = origin_weight - scenario.dest_weight.clone(); + +// let scenario = scenario.add_joiner(amount); + +// let delegator = scenario.origin1_delegator.unwrap().clone(); +// }: _(Origin::Signed(delegator.clone()), delegator.clone()) +// verify { +// assert!( +// T::StakingInterface::bonded_balance(&scenario.origin1).unwrap() +// <= scenario.dest_weight.clone() +// ); +// } + +// // TODO: setup a withdraw unbonded kill scenario +// pool_withdraw_unbonded { +// let s in 0 .. MAX_SPANS; +// clear_storage::(); + +// let min_create_bond = MinCreateBond::::get() +// .max(T::StakingInterface::minimum_bond()) +// .max(T::Currency::minimum_balance()); +// let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + +// // Add a new delegator +// let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); +// let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); +// Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) +// .unwrap(); + +// // Sanity check join worked +// assert_eq!( +// T::StakingInterface::bonded_balance(&pool_account).unwrap( + +// ), +// min_create_bond + min_join_bond +// ); +// assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); + +// // Unbond the new delegator +// Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + +// // Sanity check that unbond worked +// assert_eq!( +// T::StakingInterface::bonded_balance(&pool_account).unwrap(), +// min_create_bond +// ); +// // Set the current era +// T::StakingInterface::set_current_era(EraIndex::max_value()); + +// // T::StakingInterface::add_slashing_spans(&pool_account, s); +// whitelist_account!(pool_account); +// }: _(Origin::Signed(pool_account.clone()), pool_account.clone(), 2) +// verify { +// // The joiners funds didn't change +// assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); + +// // TODO: figure out if we can check anything else. Its tricky because the free balance hasn't +// // changed and I don't we don't have an api from here to the unlocking chunks, or staking balance +// lock } + +// // TODO: setup a withdraw unbonded kill scenario, make variable over slashing spans +// withdraw_unbonded_other { +// let s in 0 .. MAX_SPANS; +// clear_storage::(); + +// // T::StakingInterface::add_slashing_spans(&stash, s); + +// let min_create_bond = MinCreateBond::::get() +// .max(T::StakingInterface::minimum_bond()) +// .max(T::Currency::minimum_balance()); +// let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + +// // Add a new delegator +// let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); +// let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); +// Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) +// .unwrap(); + +// // Sanity check join worked +// assert_eq!( +// T::StakingInterface::bonded_balance(&pool_account).unwrap( + +// ), +// min_create_bond + min_join_bond +// ); +// assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); + +// // Unbond the new delegator +// T::StakingInterface::set_current_era(0); +// Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + +// // Sanity check that unbond worked +// assert_eq!( +// T::StakingInterface::bonded_balance(&pool_account).unwrap(), +// min_create_bond +// ); + +// // Set the current era to ensure we can withdraw unbonded funds +// T::StakingInterface::set_current_era(EraIndex::max_value()); + +// // T::StakingInterface::add_slashing_spans(&pool_account, s); +// whitelist_account!(joiner); +// }: _(Origin::Signed(joiner.clone()), joiner.clone(), 0) +// verify { +// assert_eq!( +// T::Currency::free_balance(&joiner), +// min_join_bond * 2u32.into() +// ); +// } + +// create { +// clear_storage::(); + +// let min_create_bond = MinCreateBond::::get() +// .max(T::StakingInterface::minimum_bond()) +// .max(T::Currency::minimum_balance()); +// let depositor: T::AccountId = account("depositor", USER_SEED, 0); + +// // Give the depositor some balance to bond +// T::Currency::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); + +// // Make sure no pools exist as a pre-condition for our verify checks +// assert_eq!(RewardPools::::count(), 0); +// assert_eq!(BondedPools::::count(), 0); + +// whitelist_account!(depositor); +// }: _( +// Origin::Signed(depositor.clone()), +// min_create_bond, +// 0, +// depositor.clone(), +// depositor.clone(), +// depositor.clone() +// ) +// verify { +// assert_eq!(RewardPools::::count(), 1); +// assert_eq!(BondedPools::::count(), 1); +// let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); +// assert_eq!( +// new_pool, +// BondedPoolStorage { +// points: min_create_bond, +// depositor: depositor.clone(), +// root: depositor.clone(), +// nominator: depositor.clone(), +// state_toggler: depositor.clone(), +// state: PoolState::Open, +// } +// ); +// assert_eq!( +// T::StakingInterface::bonded_balance(&pool_account), +// Some(min_create_bond) +// ); +// } + +// nominate { +// clear_storage::(); + +// // Create a pool +// let min_create_bond = MinCreateBond::::get() +// .max(T::StakingInterface::minimum_bond()) +// .max(T::Currency::minimum_balance()); +// 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 actual +// validators let validators: Vec<_> = (0..T::StakingInterface::max_nominations()) +// .map(|i| +// T::Lookup::unlookup(account("stash", USER_SEED, i)) +// ) +// .collect(); + +// whitelist_account!(depositor); +// }:_(Origin::Signed(depositor.clone()), pool_account, validators) +// verify { +// assert_eq!(RewardPools::::count(), 1); +// assert_eq!(BondedPools::::count(), 1); +// let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); +// assert_eq!( +// new_pool, +// BondedPoolStorage { +// points: min_create_bond, +// depositor: depositor.clone(), +// root: depositor.clone(), +// nominator: depositor.clone(), +// state_toggler: depositor.clone(), +// state: PoolState::Open, +// } +// ); +// assert_eq!( +// T::StakingInterface::bonded_balance(&pool_account), +// Some(min_create_bond) +// ); +// } +// } + +// frame_benchmarking::impl_benchmark_test_suite!( +// Pallet, +// crate::mock::new_test_ext(), +// crate::mock::Runtime +// ); diff --git a/frame/nomination-pools/src/benchmark_utils.rs b/frame/nomination-pools/src/benchmark_utils.rs new file mode 100644 index 0000000000000..8084bbc3156ee --- /dev/null +++ b/frame/nomination-pools/src/benchmark_utils.rs @@ -0,0 +1,23 @@ +//! Utilities for writing benchmarks. + +use crate::{BalanceOf, BondedPools, Config}; +use sp_staking::StakingInterface; + +/// Find the account ID of the pool created by `depositor`. +pub fn find_pool_account_by_depositor(depositor: &T::AccountId) -> Option { + BondedPools::::iter() + .find(|(_, bonded_pool)| bonded_pool.depositor == *depositor) + .map(|(pool_account, _)| pool_account) +} + +// Force the `pool_account` to unbond `amount`. +pub fn unbond_pool(pool_account: T::AccountId, amount: BalanceOf) -> Result<(), ()> { + T::StakingInterface::unbond(pool_account.clone(), amount).unwrap(); + + // Account pool points for the unbonded balance + BondedPools::::mutate(&pool_account, |maybe_pool| { + maybe_pool.as_mut().map(|pool| pool.points -= amount) + }) + .map(|_| ()) + .ok_or(()) +} diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 005b901bf532e..6807c976d8d46 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -281,6 +281,8 @@ use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZer use sp_staking::{EraIndex, PoolsInterface, SlashPoolArgs, SlashPoolOut, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div, vec::Vec}; +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmark_utils; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; @@ -293,7 +295,7 @@ pub mod weights; pub use pallet::*; pub use weights::WeightInfo; -type BalanceOf = +pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type SubPoolsWithEra = BoundedBTreeMap, TotalUnbondingPools>; // NOTE: this assumes the balance type u128 or smaller. @@ -377,7 +379,7 @@ pub enum PoolState { #[cfg_attr(feature = "std", derive(Clone))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] -struct BondedPoolStorage { +pub struct BondedPoolStorage { points: BalanceOf, /// See [`BondedPool::depositor`]. depositor: T::AccountId, @@ -621,7 +623,7 @@ impl RewardPool { #[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] -struct UnbondPool { +pub struct UnbondPool { points: BalanceOf, balance: BalanceOf, } @@ -646,7 +648,7 @@ impl UnbondPool { #[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] -struct SubPools { +pub struct SubPools { /// A general, era agnostic pool of funds that have fully unbonded. The pools /// of `Self::with_era` will lazily be merged into into this pool if they are /// older then `current_era - TotalUnbondingPools`. @@ -705,7 +707,7 @@ impl SubPools { /// The maximum amount of eras an unbonding pool can exist prior to being merged with the /// `no_era pool. This is guaranteed to at least be equal to the staking `UnbondingDuration`. For /// improved UX [`Config::PostUnbondingPoolsWindow`] should be configured to a non-zero value. -struct TotalUnbondingPools(PhantomData); +pub struct TotalUnbondingPools(PhantomData); impl Get for TotalUnbondingPools { fn get() -> u32 { // TODO: This may be too dangerous in the scenario bonding_duration gets decreased because @@ -758,38 +760,37 @@ pub mod pallet { /// Minimum amount to bond to join a pool. #[pallet::storage] - pub(crate) type MinJoinBond = StorageValue<_, BalanceOf, ValueQuery>; + pub type MinJoinBond = StorageValue<_, BalanceOf, ValueQuery>; /// Minimum bond required to create a pool. #[pallet::storage] - pub(crate) type MinCreateBond = StorageValue<_, BalanceOf, ValueQuery>; + pub type MinCreateBond = StorageValue<_, BalanceOf, ValueQuery>; /// Maximum number of nomination pools that can exist. If `None`, then an unbounded number of /// pools can exist. #[pallet::storage] - pub(crate) type MaxPools = StorageValue<_, u32, OptionQuery>; + pub type MaxPools = StorageValue<_, u32, OptionQuery>; /// Active delegators. #[pallet::storage] - pub(crate) type Delegators = - CountedStorageMap<_, Twox64Concat, T::AccountId, Delegator>; + pub type Delegators = CountedStorageMap<_, Twox64Concat, T::AccountId, Delegator>; /// To get or insert a pool see [`BondedPool::get`] and [`BondedPool::put`] #[pallet::storage] - pub(crate) type BondedPools = + pub type BondedPools = CountedStorageMap<_, Twox64Concat, T::AccountId, BondedPoolStorage>; /// Reward pools. This is where there rewards for each pool accumulate. When a delegators payout /// is claimed, the balance comes out fo the reward pool. Keyed by the bonded pools /// _Stash_/_Controller_. #[pallet::storage] - pub(crate) type RewardPools = + pub type RewardPools = CountedStorageMap<_, Twox64Concat, T::AccountId, RewardPool>; /// Groups of unbonding pools. Each group of unbonding pools belongs to a bonded pool, /// hence the name sub-pools. Keyed by the bonded pools _Stash_/_Controller_. #[pallet::storage] - pub(crate) type SubPoolsStorage = + pub type SubPoolsStorage = CountedStorageMap<_, Twox64Concat, T::AccountId, SubPools>; #[pallet::genesis_config] @@ -1145,6 +1146,8 @@ pub mod pallet { Ok(()) } + // TODO: [now] in addition to a min bond, we could take a fee/deposit. This discourage + // people from creating a pool when they just could be a normal nominator. /// Create a pool. /// /// Note that the pool creator will delegate `amount` to the pool and cannot unbond until From c7ad805eb1c907e5eeb3be926994bd74dfec0fbe Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 21 Feb 2022 15:00:28 -0800 Subject: [PATCH 119/299] Get claim payout and nominate benchmarks working --- .../nomination-pools/benchmarking/Cargo.toml | 1 + .../nomination-pools/benchmarking/src/lib.rs | 125 +++++++++--------- .../nomination-pools/benchmarking/src/mock.rs | 13 +- frame/nomination-pools/src/benchmark_utils.rs | 16 ++- 4 files changed, 86 insertions(+), 69 deletions(-) diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index 549b90f47daf8..f14986372764e 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -47,6 +47,7 @@ std = [ "frame-election-provider-support/std", "pallet-balances/std", "pallet-staking/std", + "pallet-nomination-pools/std", "sp-runtime/std", "sp-staking/std" ] \ No newline at end of file diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index f10916d3c72c7..b8ff11572b996 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -5,19 +5,18 @@ use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelis use frame_support::ensure; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ - benchmark_utils::{find_pool_account_by_depositor, unbond_pool}, + benchmark_utils::{find_pool_account_by_depositor, unbond_pool, get_reward_pool_account}, BalanceOf, Pallet as Pools, }; -use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZeroInput, Zero}; +use sp_runtime::traits::{StaticLookup, Zero}; use sp_staking::StakingInterface; +// Benchmark macro code needs this +use pallet_nomination_pools::Call; + const USER_SEED: u32 = 0; const MAX_SPANS: u32 = 100; -// pub trait NominationPoolsBenchConfig: -// pallet_nomination_pools::Config + pallet_staking::Config + pallet_bags_list::Config -// { -// } pub trait Config: pallet_nomination_pools::Config + pallet_staking::Config + pallet_bags_list::Config { @@ -166,70 +165,66 @@ impl ListScenario { } } -// frame_benchmarking::benchmarks! { -// join { -// clear_storage::(); +frame_benchmarking::benchmarks! { + join { + clear_storage::(); -// let origin_weight = -// pallet_nomination_pools::MinCreateBond::::get().max(T::Currency::minimum_balance()) * -// 2u32.into(); + let origin_weight = pallet_nomination_pools::MinCreateBond::::get() + .max(::Currency::minimum_balance()) + * 2u32.into(); -// // setup the worst case list scenario. -// let scenario = ListScenario::::new(origin_weight, true)?; -// assert_eq!( -// T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), -// origin_weight -// ); + // setup the worst case list scenario. + let scenario = ListScenario::::new(origin_weight, true)?; + assert_eq!( + T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), + origin_weight + ); -// let max_additional = scenario.dest_weight.clone() - origin_weight; -// let joiner_free = T::Currency::minimum_balance() + max_additional; + let max_additional = scenario.dest_weight.clone() - origin_weight; + let joiner_free = ::Currency::minimum_balance() + max_additional; -// let joiner: T::AccountId -// = create_funded_user_with_balance::("joiner", 0, joiner_free); + let joiner: T::AccountId + = create_funded_user_with_balance::("joiner", 0, joiner_free); -// whitelist_account!(joiner); -// }: _(Origin::Signed(joiner.clone()), max_additional, scenario.origin1.clone()) -// verify { -// assert_eq!(T::Currency::free_balance(&joiner), joiner_free - max_additional); -// assert_eq!( -// T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), -// scenario.dest_weight -// ); -// } + whitelist_account!(joiner); + }: _(Origin::Signed(joiner.clone()), max_additional, scenario.origin1.clone()) + verify { + assert_eq!(::Currency::free_balance(&joiner), joiner_free - max_additional); + assert_eq!( + T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), + scenario.dest_weight + ); + } -// claim_payout { -// clear_storage::(); + claim_payout { + clear_storage::(); -// let origin_weight = MinCreateBond::::get().max(T::Currency::minimum_balance()) * 2u32.into(); -// let (depositor, pool_account) = create_pool_account::(0, origin_weight); + let origin_weight = pallet_nomination_pools::MinCreateBond::::get().max(::Currency::minimum_balance()) * 2u32.into(); + let (depositor, pool_account) = create_pool_account::(0, origin_weight); -// let reward_account = RewardPools::::get( -// pool_account -// ) -// .unwrap() -// .account; + let reward_account = get_reward_pool_account::(&pool_account).expect("pool created above."); -// // Send funds to the reward account of the pool -// T::Currency::make_free_balance_be(&reward_account, origin_weight); + // Send funds to the reward account of the pool + ::Currency::make_free_balance_be(&reward_account, origin_weight); -// // Sanity check -// assert_eq!( -// T::Currency::free_balance(&depositor), -// origin_weight -// ); + // Sanity check + assert_eq!( + ::Currency::free_balance(&depositor), + origin_weight + ); -// whitelist_account!(depositor); -// }:_(Origin::Signed(depositor.clone())) -// verify { -// assert_eq!( -// T::Currency::free_balance(&depositor), -// origin_weight * 2u32.into() -// ); -// assert_eq!( -// T::Currency::free_balance(&reward_account), -// Zero::zero() -// ); -// } + whitelist_account!(depositor); + }:_(Origin::Signed(depositor.clone())) + verify { + assert_eq!( + ::Currency::free_balance(&depositor), + origin_weight * 2u32.into() + ); + assert_eq!( + ::Currency::free_balance(&reward_account), + Zero::zero() + ); + } // unbond_other { // clear_storage::(); @@ -434,10 +429,10 @@ impl ListScenario { // Some(min_create_bond) // ); // } -// } +} -// frame_benchmarking::impl_benchmark_test_suite!( -// Pallet, -// crate::mock::new_test_ext(), -// crate::mock::Runtime -// ); +frame_benchmarking::impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext(), + crate::mock::Runtime +); diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 12f471482ebeb..cbb9fb828619e 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -152,6 +152,8 @@ impl pallet_nomination_pools::Config for Runtime { type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; } +impl crate::Config for Runtime {} + impl frame_system::offchain::SendTransactionTypes for Runtime where Call: From, @@ -178,6 +180,13 @@ frame_support::construct_runtime!( ); pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - sp_io::TestExternalities::new(t) + 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), + } + .assimilate_storage(&mut storage); + sp_io::TestExternalities::from(storage) } diff --git a/frame/nomination-pools/src/benchmark_utils.rs b/frame/nomination-pools/src/benchmark_utils.rs index 8084bbc3156ee..7dd749f9b1e14 100644 --- a/frame/nomination-pools/src/benchmark_utils.rs +++ b/frame/nomination-pools/src/benchmark_utils.rs @@ -1,8 +1,10 @@ //! Utilities for writing benchmarks. -use crate::{BalanceOf, BondedPools, Config}; +use crate::{BalanceOf, BondedPools, Config, RewardPools}; use sp_staking::StakingInterface; +// TODO: [now] most of these can be achieved by making the fields on some structs public + /// Find the account ID of the pool created by `depositor`. pub fn find_pool_account_by_depositor(depositor: &T::AccountId) -> Option { BondedPools::::iter() @@ -12,7 +14,7 @@ pub fn find_pool_account_by_depositor(depositor: &T::AccountId) -> Op // Force the `pool_account` to unbond `amount`. pub fn unbond_pool(pool_account: T::AccountId, amount: BalanceOf) -> Result<(), ()> { - T::StakingInterface::unbond(pool_account.clone(), amount).unwrap(); + T::StakingInterface::unbond(pool_account.clone(), amount).map_err(|_| ())?; // Account pool points for the unbonded balance BondedPools::::mutate(&pool_account, |maybe_pool| { @@ -21,3 +23,13 @@ pub fn unbond_pool(pool_account: T::AccountId, amount: BalanceOf) .map(|_| ()) .ok_or(()) } + +// Get the account ID of the reward pool for the given bonded `pool_account`. +pub fn get_reward_pool_account(pool_account: &T::AccountId) -> Result { + RewardPools::::get( + pool_account + ) + .map(|r| r.account.clone()) + .ok_or(()) +} From 118d9cfcea09f3fed2b30f6b1f06e2e5c12ad6a2 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 21 Feb 2022 15:12:12 -0800 Subject: [PATCH 120/299] Remove pool bench utils; make struct fields pub insteaad --- .../nomination-pools/benchmarking/src/lib.rs | 28 +++++++++------ .../nomination-pools/benchmarking/src/mock.rs | 3 +- frame/nomination-pools/src/benchmark_utils.rs | 35 ------------------- frame/nomination-pools/src/lib.rs | 23 ++++++------ 4 files changed, 29 insertions(+), 60 deletions(-) delete mode 100644 frame/nomination-pools/src/benchmark_utils.rs diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index b8ff11572b996..e53a1848041fc 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -4,10 +4,7 @@ mod mock; use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelist_account}; use frame_support::ensure; use frame_system::RawOrigin as Origin; -use pallet_nomination_pools::{ - benchmark_utils::{find_pool_account_by_depositor, unbond_pool, get_reward_pool_account}, - BalanceOf, Pallet as Pools, -}; +use pallet_nomination_pools::{BalanceOf, Pallet as Pools}; use sp_runtime::traits::{StaticLookup, Zero}; use sp_staking::StakingInterface; @@ -61,7 +58,9 @@ fn create_pool_account( ) .unwrap(); - let pool_account = find_pool_account_by_depositor::(&pool_creator) + let pool_account = pallet_nomination_pools::BondedPools::::iter() + .find(|(_, bonded_pool)| bonded_pool.depositor == pool_creator) + .map(|(pool_account, _)| pool_account) .expect("pool_creator created a pool above"); (pool_creator, pool_account) @@ -119,14 +118,14 @@ impl ListScenario { vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))].clone(), )?; - // find a destination weight that will trigger the worst case scenario + // Find a destination weight that will trigger the worst case scenario let dest_weight_as_vote = T::StakingInterface::weight_update_worst_case(&pool_origin1, is_increase); let dest_weight: BalanceOf = dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; - // create an account with the worst case destination weight + // Create an account with the worst case destination weight let (_, pool_dest1) = create_pool_account::(USER_SEED + 1, dest_weight); T::StakingInterface::nominate( pool_dest1, @@ -139,7 +138,7 @@ impl ListScenario { fn add_joiner(mut self, amount: BalanceOf) -> Self { let amount = pallet_nomination_pools::MinJoinBond::::get() .max(::Currency::minimum_balance()) - // max the `given` amount with minimum thresholds for account balance and joining a pool + // Max the `given` amount with minimum thresholds for account balance and joining a pool // to ensure 1. the user can be created and 2. can join the pool .max(amount); @@ -153,8 +152,13 @@ impl ListScenario { let current_bonded = T::StakingInterface::bonded_balance(&self.origin1).unwrap(); // Unbond `amount` from the underlying pool account so when the delegator joins - // we will maintain `current_bonded` - unbond_pool::(self.origin1.clone(), amount); + // we will maintain `current_bonded`. + T::StakingInterface::unbond(self.origin1.clone(), amount); + + // Account pool points for the unbonded balance. + pallet_nomination_pools::BondedPools::::mutate(&self.origin1, |maybe_pool| { + maybe_pool.as_mut().map(|pool| pool.points -= amount) + }); Pools::::join(Origin::Signed(joiner.clone()).into(), amount, self.origin1.clone()) .unwrap(); @@ -202,7 +206,9 @@ frame_benchmarking::benchmarks! { let origin_weight = pallet_nomination_pools::MinCreateBond::::get().max(::Currency::minimum_balance()) * 2u32.into(); let (depositor, pool_account) = create_pool_account::(0, origin_weight); - let reward_account = get_reward_pool_account::(&pool_account).expect("pool created above."); + let reward_account = pallet_nomination_pools::RewardPools::::get(pool_account) + .map(|r| r.account.clone()) + .expect("pool created above."); // Send funds to the reward account of the pool ::Currency::make_free_balance_be(&reward_account, origin_weight); diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index cbb9fb828619e..55312b036bab1 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -180,8 +180,7 @@ frame_support::construct_runtime!( ); pub fn new_test_ext() -> sp_io::TestExternalities { - 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: 2, min_create_bond: 2, diff --git a/frame/nomination-pools/src/benchmark_utils.rs b/frame/nomination-pools/src/benchmark_utils.rs deleted file mode 100644 index 7dd749f9b1e14..0000000000000 --- a/frame/nomination-pools/src/benchmark_utils.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Utilities for writing benchmarks. - -use crate::{BalanceOf, BondedPools, Config, RewardPools}; -use sp_staking::StakingInterface; - -// TODO: [now] most of these can be achieved by making the fields on some structs public - -/// Find the account ID of the pool created by `depositor`. -pub fn find_pool_account_by_depositor(depositor: &T::AccountId) -> Option { - BondedPools::::iter() - .find(|(_, bonded_pool)| bonded_pool.depositor == *depositor) - .map(|(pool_account, _)| pool_account) -} - -// Force the `pool_account` to unbond `amount`. -pub fn unbond_pool(pool_account: T::AccountId, amount: BalanceOf) -> Result<(), ()> { - T::StakingInterface::unbond(pool_account.clone(), amount).map_err(|_| ())?; - - // Account pool points for the unbonded balance - BondedPools::::mutate(&pool_account, |maybe_pool| { - maybe_pool.as_mut().map(|pool| pool.points -= amount) - }) - .map(|_| ()) - .ok_or(()) -} - -// Get the account ID of the reward pool for the given bonded `pool_account`. -pub fn get_reward_pool_account(pool_account: &T::AccountId) -> Result { - RewardPools::::get( - pool_account - ) - .map(|r| r.account.clone()) - .ok_or(()) -} diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 6807c976d8d46..12124199b421a 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -281,8 +281,6 @@ use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZer use sp_staking::{EraIndex, PoolsInterface, SlashPoolArgs, SlashPoolOut, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div, vec::Vec}; -#[cfg(feature = "runtime-benchmarks")] -pub mod benchmark_utils; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; @@ -380,17 +378,18 @@ pub enum PoolState { #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct BondedPoolStorage { - points: BalanceOf, + /// See [`BondedPool::points`]. + pub points: BalanceOf, /// See [`BondedPool::depositor`]. - depositor: T::AccountId, + pub depositor: T::AccountId, /// See [`BondedPool::admin`]. - root: T::AccountId, + pub root: T::AccountId, /// See [`BondedPool::nominator`]. - nominator: T::AccountId, + pub nominator: T::AccountId, /// See [`BondedPool::state_toggler`]. - state_toggler: T::AccountId, + pub state_toggler: T::AccountId, /// See [`BondedPool::state_toggler`]. - state: PoolState, + pub state: PoolState, } #[derive(RuntimeDebugNoBound)] @@ -593,18 +592,18 @@ impl BondedPool { #[scale_info(skip_type_params(T))] pub struct RewardPool { /// The reward destination for the pool. - account: T::AccountId, + pub account: T::AccountId, /// The balance of this reward pool after the last claimed payout. - balance: BalanceOf, + pub balance: BalanceOf, /// The total earnings _ever_ of this reward pool after the last claimed payout. I.E. the sum /// of all incoming balance through the pools life. /// /// NOTE: We assume this will always be less than total issuance and thus can use the runtimes /// `Balance` type. However in a chain with a burn rate higher than the rate this increases, /// this type should be bigger than `Balance`. - total_earnings: BalanceOf, + pub total_earnings: BalanceOf, /// The total points of this reward pool after the last claimed payout. - points: RewardPoints, + pub points: RewardPoints, } impl RewardPool { From 7c97d651014f2e0cc623098b117643862eaceee6 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 21 Feb 2022 20:05:55 -0800 Subject: [PATCH 121/299] Get more benchmarks to work; trim interface trait --- Cargo.lock | 1 + .../nomination-pools/benchmarking/Cargo.toml | 3 + .../nomination-pools/benchmarking/src/lib.rs | 280 +++--- .../nomination-pools/benchmarking/src/mock.rs | 5 +- frame/nomination-pools/src/benchmarking.rs | 844 +++++++++--------- frame/nomination-pools/src/lib.rs | 11 +- frame/staking/src/benchmarking.rs | 2 +- frame/staking/src/pallet/impls.rs | 15 - primitives/staking/src/lib.rs | 11 - 9 files changed, 592 insertions(+), 580 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91c47504c8d28..cccc986a9a14e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6084,6 +6084,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-staking", + "sp-tracing", ] [[package]] diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index f14986372764e..ef49f66fe891e 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -30,6 +30,9 @@ pallet-nomination-pools = { version = "1.0.0", default-features = false, feature sp-runtime = { version = "5.0.0", default-features = false, path = "../../../primitives/runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } +# TODO: remove +sp-tracing = { version = "4.0.0", path = "../../../primitives/tracing" } + [dev-dependencies] pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index e53a1848041fc..a39a6499779e2 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -1,16 +1,22 @@ +//! Benchmarks for the nomination pools coupled with the staking and bags list pallets. + #[cfg(test)] mod mock; use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelist_account}; +use frame_election_provider_support::SortedListProvider; use frame_support::ensure; use frame_system::RawOrigin as Origin; -use pallet_nomination_pools::{BalanceOf, Pallet as Pools}; +use pallet_nomination_pools::MinCreateBond; +use pallet_nomination_pools::{BalanceOf, BondedPools, Delegators, MinJoinBond, Pallet as Pools}; use sp_runtime::traits::{StaticLookup, Zero}; -use sp_staking::StakingInterface; +use sp_staking::{EraIndex, StakingInterface}; -// Benchmark macro code needs this +// `frame_benchmarking::benchmarks!` macro code needs this use pallet_nomination_pools::Call; +type CurrencyOf = ::Currency; + const USER_SEED: u32 = 0; const MAX_SPANS: u32 = 100; @@ -73,7 +79,8 @@ struct ListScenario { origin1_delegator: Option, } -impl ListScenario { +impl ListScenario { /// An expensive scenario for bags-list implementation: /// /// - the node to be updated (r) is the head of a bag that has at least one other node. The bag @@ -120,7 +127,10 @@ impl ListScenario { // Find a destination weight that will trigger the worst case scenario let dest_weight_as_vote = - T::StakingInterface::weight_update_worst_case(&pool_origin1, is_increase); + ::SortedListProvider::weight_update_worst_case( + &pool_origin1, + is_increase, + ); let dest_weight: BalanceOf = dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; @@ -136,34 +146,32 @@ impl ListScenario { } fn add_joiner(mut self, amount: BalanceOf) -> Self { - let amount = pallet_nomination_pools::MinJoinBond::::get() - .max(::Currency::minimum_balance()) + let amount = MinJoinBond::::get() + .max(CurrencyOf::::minimum_balance()) // Max the `given` amount with minimum thresholds for account balance and joining a pool // to ensure 1. the user can be created and 2. can join the pool .max(amount); let joiner: T::AccountId = account("joiner", USER_SEED, 0); self.origin1_delegator = Some(joiner.clone()); - ::Currency::make_free_balance_be( - &joiner, - amount * 2u32.into(), - ); + CurrencyOf::::make_free_balance_be(&joiner, amount * 2u32.into()); - let current_bonded = T::StakingInterface::bonded_balance(&self.origin1).unwrap(); + let original_bonded = T::StakingInterface::bonded_balance(&self.origin1).unwrap(); // Unbond `amount` from the underlying pool account so when the delegator joins // we will maintain `current_bonded`. - T::StakingInterface::unbond(self.origin1.clone(), amount); + T::StakingInterface::unbond(self.origin1.clone(), amount) + .expect("the pool was created in `Self::new`."); // Account pool points for the unbonded balance. - pallet_nomination_pools::BondedPools::::mutate(&self.origin1, |maybe_pool| { + BondedPools::::mutate(&self.origin1, |maybe_pool| { maybe_pool.as_mut().map(|pool| pool.points -= amount) }); Pools::::join(Origin::Signed(joiner.clone()).into(), amount, self.origin1.clone()) .unwrap(); - assert_eq!(T::StakingInterface::bonded_balance(&self.origin1).unwrap(), current_bonded); + assert_eq!(T::StakingInterface::bonded_balance(&self.origin1).unwrap(), original_bonded); self } @@ -174,7 +182,7 @@ frame_benchmarking::benchmarks! { clear_storage::(); let origin_weight = pallet_nomination_pools::MinCreateBond::::get() - .max(::Currency::minimum_balance()) + .max(CurrencyOf::::minimum_balance()) * 2u32.into(); // setup the worst case list scenario. @@ -185,7 +193,7 @@ frame_benchmarking::benchmarks! { ); let max_additional = scenario.dest_weight.clone() - origin_weight; - let joiner_free = ::Currency::minimum_balance() + max_additional; + let joiner_free = CurrencyOf::::minimum_balance() + max_additional; let joiner: T::AccountId = create_funded_user_with_balance::("joiner", 0, joiner_free); @@ -193,7 +201,7 @@ frame_benchmarking::benchmarks! { whitelist_account!(joiner); }: _(Origin::Signed(joiner.clone()), max_additional, scenario.origin1.clone()) verify { - assert_eq!(::Currency::free_balance(&joiner), joiner_free - max_additional); + assert_eq!(CurrencyOf::::free_balance(&joiner), joiner_free - max_additional); assert_eq!( T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), scenario.dest_weight @@ -203,7 +211,7 @@ frame_benchmarking::benchmarks! { claim_payout { clear_storage::(); - let origin_weight = pallet_nomination_pools::MinCreateBond::::get().max(::Currency::minimum_balance()) * 2u32.into(); + let origin_weight = pallet_nomination_pools::MinCreateBond::::get().max(CurrencyOf::::minimum_balance()) * 2u32.into(); let (depositor, pool_account) = create_pool_account::(0, origin_weight); let reward_account = pallet_nomination_pools::RewardPools::::get(pool_account) @@ -211,11 +219,11 @@ frame_benchmarking::benchmarks! { .expect("pool created above."); // Send funds to the reward account of the pool - ::Currency::make_free_balance_be(&reward_account, origin_weight); + CurrencyOf::::make_free_balance_be(&reward_account, origin_weight); // Sanity check assert_eq!( - ::Currency::free_balance(&depositor), + CurrencyOf::::free_balance(&depositor), origin_weight ); @@ -223,134 +231,160 @@ frame_benchmarking::benchmarks! { }:_(Origin::Signed(depositor.clone())) verify { assert_eq!( - ::Currency::free_balance(&depositor), + CurrencyOf::::free_balance(&depositor), origin_weight * 2u32.into() ); assert_eq!( - ::Currency::free_balance(&reward_account), + CurrencyOf::::free_balance(&reward_account), Zero::zero() ); } -// unbond_other { -// clear_storage::(); - -// // the weight the nominator will start at. The value used here is expected to be -// // significantly higher than the first position in a list (e.g. the first bag threshold). -// let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) -// .map_err(|_| "balance expected to be a u128") -// .unwrap(); -// let scenario = ListScenario::::new(origin_weight, false)?; - -// let amount = origin_weight - scenario.dest_weight.clone(); - -// let scenario = scenario.add_joiner(amount); + unbond_other { + clear_storage::(); -// let delegator = scenario.origin1_delegator.unwrap().clone(); -// }: _(Origin::Signed(delegator.clone()), delegator.clone()) -// verify { -// assert!( -// T::StakingInterface::bonded_balance(&scenario.origin1).unwrap() -// <= scenario.dest_weight.clone() -// ); -// } + // the weight the nominator will start at. The value used here is expected to be + // significantly higher than the first position in a list (e.g. the first bag threshold). + let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) + .map_err(|_| "balance expected to be a u128") + .unwrap(); + let scenario = ListScenario::::new(origin_weight, false)?; -// // TODO: setup a withdraw unbonded kill scenario -// pool_withdraw_unbonded { -// let s in 0 .. MAX_SPANS; -// clear_storage::(); + let amount = origin_weight - scenario.dest_weight.clone(); -// let min_create_bond = MinCreateBond::::get() -// .max(T::StakingInterface::minimum_bond()) -// .max(T::Currency::minimum_balance()); -// let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + let scenario = scenario.add_joiner(amount); + let delegator_id = scenario.origin1_delegator.unwrap().clone(); -// // Add a new delegator -// let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); -// let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); -// Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) -// .unwrap(); + // Sanity check the delegator was added correctly + let delegator = Delegators::::get( + &delegator_id + ) + .unwrap(); + assert_eq!( + delegator.points, + amount + ); + assert_eq!( + delegator.pool, + scenario.origin1 + ); + // Sanity check the pool maintained the origin weight + let bonded_pool = BondedPools::::get(scenario.origin1.clone()).unwrap(); + assert_eq!( + bonded_pool.points, + origin_weight + ); + let bonded_before = T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(); + assert_eq!( + bonded_before, + origin_weight + ); -// // Sanity check join worked -// assert_eq!( -// T::StakingInterface::bonded_balance(&pool_account).unwrap( + }: _(Origin::Signed(delegator_id.clone()), delegator_id.clone()) + verify { + let bonded_after = T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(); + // We at least went down to the destination bag, (if not an even lower bag) + assert!(bonded_after <= scenario.dest_weight.clone()); + let delegator = Delegators::::get( + &delegator_id + ) + .unwrap(); + assert_eq!(delegator.unbonding_era, Some(0)); + } -// ), -// min_create_bond + min_join_bond -// ); -// assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); + // TODO: setup a withdraw unbonded kill scenario + pool_withdraw_unbonded { + let s in 0 .. MAX_SPANS; + clear_storage::(); -// // Unbond the new delegator -// Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); -// // Sanity check that unbond worked -// assert_eq!( -// T::StakingInterface::bonded_balance(&pool_account).unwrap(), -// min_create_bond -// ); -// // Set the current era -// T::StakingInterface::set_current_era(EraIndex::max_value()); + // Add a new delegator + let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); + let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); + Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) + .unwrap(); -// // T::StakingInterface::add_slashing_spans(&pool_account, s); -// whitelist_account!(pool_account); -// }: _(Origin::Signed(pool_account.clone()), pool_account.clone(), 2) -// verify { -// // The joiners funds didn't change -// assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); + // Sanity check join worked + assert_eq!( + T::StakingInterface::bonded_balance(&pool_account).unwrap(), + min_create_bond + min_join_bond + ); + assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); -// // TODO: figure out if we can check anything else. Its tricky because the free balance hasn't -// // changed and I don't we don't have an api from here to the unlocking chunks, or staking balance -// lock } + // Unbond the new delegator + Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); -// // TODO: setup a withdraw unbonded kill scenario, make variable over slashing spans -// withdraw_unbonded_other { -// let s in 0 .. MAX_SPANS; -// clear_storage::(); + // Sanity check that unbond worked + assert_eq!( + T::StakingInterface::bonded_balance(&pool_account).unwrap(), + min_create_bond + ); + assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); + // Set the current era + pallet_staking::CurrentEra::::put(EraIndex::max_value()); + + // Add `s` count of slashing spans to storage. + pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); + whitelist_account!(pool_account); + }: _(Origin::Signed(pool_account.clone()), pool_account.clone(), s) + verify { + // The joiners funds didn't change + assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); + // The unlocking chunk was removed + assert_eq!(pallet_staking::Ledger::::get(pool_account).unwrap().unlocking.len(), 0); + } -// // T::StakingInterface::add_slashing_spans(&stash, s); + // TODO: setup a withdraw unbonded kill scenario, make variable over slashing spans + withdraw_unbonded_other { + let s in 0 .. MAX_SPANS; + clear_storage::(); -// let min_create_bond = MinCreateBond::::get() -// .max(T::StakingInterface::minimum_bond()) -// .max(T::Currency::minimum_balance()); -// let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + // T::StakingInterface::add_slashing_spans(&stash, s); -// // Add a new delegator -// let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); -// let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); -// Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) -// .unwrap(); + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); -// // Sanity check join worked -// assert_eq!( -// T::StakingInterface::bonded_balance(&pool_account).unwrap( + // Add a new delegator + let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); + let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); + Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) + .unwrap(); -// ), -// min_create_bond + min_join_bond -// ); -// assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); + // Sanity check join worked + assert_eq!( + T::StakingInterface::bonded_balance(&pool_account).unwrap(), + min_create_bond + min_join_bond + ); + assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); -// // Unbond the new delegator -// T::StakingInterface::set_current_era(0); -// Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + // Unbond the new delegator + pallet_staking::CurrentEra::::put(0); + Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); -// // Sanity check that unbond worked -// assert_eq!( -// T::StakingInterface::bonded_balance(&pool_account).unwrap(), -// min_create_bond -// ); + // Sanity check that unbond worked + assert_eq!( + T::StakingInterface::bonded_balance(&pool_account).unwrap(), + min_create_bond + ); -// // Set the current era to ensure we can withdraw unbonded funds -// T::StakingInterface::set_current_era(EraIndex::max_value()); + // Set the current era to ensure we can withdraw unbonded funds + pallet_staking::CurrentEra::::put(EraIndex::max_value()); -// // T::StakingInterface::add_slashing_spans(&pool_account, s); -// whitelist_account!(joiner); -// }: _(Origin::Signed(joiner.clone()), joiner.clone(), 0) -// verify { -// assert_eq!( -// T::Currency::free_balance(&joiner), -// min_join_bond * 2u32.into() -// ); -// } + // T::StakingInterface::add_slashing_spans(&pool_account, s); + whitelist_account!(joiner); + }: _(Origin::Signed(joiner.clone()), joiner.clone(), 0) + verify { + assert_eq!( + CurrencyOf::::free_balance(&joiner), + min_join_bond * 2u32.into() + ); + } // create { // clear_storage::(); @@ -407,7 +441,7 @@ frame_benchmarking::benchmarks! { // 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 actual -// validators let validators: Vec<_> = (0..T::StakingInterface::max_nominations()) +// validators let validators: Vec<_> = (0..T::MaxNominations::get()) // .map(|i| // T::Lookup::unlookup(account("stash", USER_SEED, i)) // ) diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 55312b036bab1..4582df333f631 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -22,7 +22,7 @@ use sp_runtime::traits::{Convert, IdentityLookup}; type AccountId = u64; type AccountIndex = u32; type BlockNumber = u64; -type Balance = u64; +type Balance = u128; impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; @@ -42,7 +42,7 @@ impl frame_system::Config for Runtime { type BlockHashCount = (); type Version = (); type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); @@ -180,6 +180,7 @@ frame_support::construct_runtime!( ); 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, diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index ffbe7376e2fea..3d4e6e9f80397 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -1,422 +1,422 @@ -use super::*; -use crate::Pallet as Pools; -use frame_benchmarking::{account, vec, whitelist_account}; -use frame_system::RawOrigin as Origin; - -const USER_SEED: u32 = 0; -const MAX_SPANS: u32 = 100; - -fn clear_storage() { - BondedPools::::remove_all(); - RewardPools::::remove_all(); - SubPoolsStorage::::remove_all(); - Delegators::::remove_all(); -} - -fn create_funded_user_with_balance( - string: &'static str, - n: u32, - balance: BalanceOf, -) -> T::AccountId { - let user = account(string, n, USER_SEED); - T::Currency::make_free_balance_be(&user, balance); - user -} - -// Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free -// balance. -fn create_pool_account(n: u32, balance: BalanceOf) -> (T::AccountId, T::AccountId) { - let pool_creator: T::AccountId = - create_funded_user_with_balance::("pool_creator", n, balance * 2u32.into()); - - Pools::::create( - Origin::Signed(pool_creator.clone()).into(), - balance, - n as u16, - pool_creator.clone(), - pool_creator.clone(), - pool_creator.clone(), - ) - .unwrap(); - - let (pool_account, _) = BondedPools::::iter() - .find(|(_, bonded_pool)| bonded_pool.depositor == pool_creator) - .expect("pool_creator created a pool above"); - - (pool_creator, pool_account) -} - -struct ListScenario { - /// Stash/Controller that is expected to be moved. - origin1: T::AccountId, - dest_weight: BalanceOf, - origin1_delegator: Option, -} - -impl ListScenario { - /// An expensive scenario for bags-list implementation: - /// - /// - the node to be updated (r) is the head of a bag that has at least one other node. The bag - /// itself will need to be read and written to update its head. The node pointed to by r.next - /// will need to be read and written as it will need to have its prev pointer updated. Note - /// that there are two other worst case scenarios for bag removal: 1) the node is a tail and - /// 2) the node is a middle node with prev and next; all scenarios end up with the same number - /// of storage reads and writes. - /// - /// - the destination bag has at least one node, which will need its next pointer updated. - /// - /// NOTE: while this scenario specifically targets a worst case for the bags-list, it should - /// also elicit a worst case for other known `SortedListProvider` implementations; although - /// this may not be true against unknown `SortedListProvider` implementations. - pub(crate) fn new( - origin_weight: BalanceOf, - is_increase: bool, - ) -> Result { - ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); - - if MaxPools::::get().unwrap_or(0) < 3 { - // TODO: this is due to mock config, probably best to change - MaxPools::::put(3); - } - - // Burn the entire issuance. - // TODO: probably don't need this - let i = T::Currency::burn(T::Currency::total_issuance()); - sp_std::mem::forget(i); - - // create accounts with the origin weight - let (_, pool_origin1) = create_pool_account::(USER_SEED + 2, origin_weight); - T::StakingInterface::nominate( - pool_origin1.clone(), - // NOTE: these don't really need to be validators. - vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], - )?; - - let (_, pool_origin2) = create_pool_account::(USER_SEED + 3, origin_weight); - T::StakingInterface::nominate( - pool_origin2, - vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))].clone(), - )?; - - // find a destination weight that will trigger the worst case scenario - let dest_weight_as_vote = - T::StakingInterface::weight_update_worst_case(&pool_origin1, is_increase); - - let dest_weight: BalanceOf = - dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; - - // create an account with the worst case destination weight - let (_, pool_dest1) = create_pool_account::(USER_SEED + 1, dest_weight); - T::StakingInterface::nominate( - pool_dest1, - vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], - )?; - - Ok(ListScenario { origin1: pool_origin1, dest_weight, origin1_delegator: None }) - } - - fn add_joiner(mut self, amount: BalanceOf) -> Self { - let amount = MinJoinBond::::get() - .max(T::Currency::minimum_balance()) - // max the `given` amount with minimum thresholds for account balance and joining a pool - // to ensure 1. the user can be created and 2. can join the pool - .max(amount); - - let joiner: T::AccountId = account("joiner", USER_SEED, 0); - self.origin1_delegator = Some(joiner.clone()); - T::Currency::make_free_balance_be(&joiner, amount * 2u32.into()); - - let current_bonded = T::StakingInterface::bonded_balance(&self.origin1).unwrap(); - - // Unbond `amount` from the underlying pool account so when the delegator joins - // we will maintain `current_bonded` - T::StakingInterface::unbond(self.origin1.clone(), amount).unwrap(); - - // Account pool points for the unbonded balance - BondedPools::::mutate(&self.origin1, |maybe_pool| { - maybe_pool.as_mut().map(|pool| pool.points -= amount) - }); - - Pools::::join(Origin::Signed(joiner.clone()).into(), amount, self.origin1.clone()) - .unwrap(); - - assert_eq!(T::StakingInterface::bonded_balance(&self.origin1).unwrap(), current_bonded); - - self - } -} - -frame_benchmarking::benchmarks! { - join { - clear_storage::(); - - let origin_weight = MinCreateBond::::get().max(T::Currency::minimum_balance()) * 2u32.into(); - - // setup the worst case list scenario. - let scenario = ListScenario::::new(origin_weight, true)?; - assert_eq!( - T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), - origin_weight - ); - - - let max_additional = scenario.dest_weight.clone() - origin_weight; - let joiner_free = T::Currency::minimum_balance() + max_additional; - - let joiner: T::AccountId - = create_funded_user_with_balance::("joiner", 0, joiner_free); - - whitelist_account!(joiner); - }: _(Origin::Signed(joiner.clone()), max_additional, scenario.origin1.clone()) - verify { - assert_eq!(T::Currency::free_balance(&joiner), joiner_free - max_additional); - assert_eq!( - T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), - scenario.dest_weight - ); - } - - claim_payout { - clear_storage::(); - - let origin_weight = MinCreateBond::::get().max(T::Currency::minimum_balance()) * 2u32.into(); - let (depositor, pool_account) = create_pool_account::(0, origin_weight); - - let reward_account = RewardPools::::get( - pool_account - ) - .unwrap() - .account; - - // Send funds to the reward account of the pool - T::Currency::make_free_balance_be(&reward_account, origin_weight); - - // Sanity check - assert_eq!( - T::Currency::free_balance(&depositor), - origin_weight - ); - - whitelist_account!(depositor); - }:_(Origin::Signed(depositor.clone())) - verify { - assert_eq!( - T::Currency::free_balance(&depositor), - origin_weight * 2u32.into() - ); - assert_eq!( - T::Currency::free_balance(&reward_account), - Zero::zero() - ); - } - - unbond_other { - clear_storage::(); - - // the weight the nominator will start at. The value used here is expected to be - // significantly higher than the first position in a list (e.g. the first bag threshold). - let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) - .map_err(|_| "balance expected to be a u128") - .unwrap(); - let scenario = ListScenario::::new(origin_weight, false)?; - - let amount = origin_weight - scenario.dest_weight.clone(); - - let scenario = scenario.add_joiner(amount); - - let delegator = scenario.origin1_delegator.unwrap().clone(); - }: _(Origin::Signed(delegator.clone()), delegator.clone()) - verify { - assert!( - T::StakingInterface::bonded_balance(&scenario.origin1).unwrap() - <= scenario.dest_weight.clone() - ); - } - - // TODO: setup a withdraw unbonded kill scenario - pool_withdraw_unbonded { - let s in 0 .. MAX_SPANS; - clear_storage::(); - - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(T::Currency::minimum_balance()); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond); - - // Add a new delegator - let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); - let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); - Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) - .unwrap(); - - // Sanity check join worked - assert_eq!( - T::StakingInterface::bonded_balance(&pool_account).unwrap( - - ), - min_create_bond + min_join_bond - ); - assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); - - // Unbond the new delegator - Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); - - // Sanity check that unbond worked - assert_eq!( - T::StakingInterface::bonded_balance(&pool_account).unwrap(), - min_create_bond - ); - // Set the current era - T::StakingInterface::set_current_era(EraIndex::max_value()); - - // T::StakingInterface::add_slashing_spans(&pool_account, s); - whitelist_account!(pool_account); - }: _(Origin::Signed(pool_account.clone()), pool_account.clone(), 2) - verify { - // The joiners funds didn't change - assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); - - // TODO: figure out if we can check anything else. Its tricky because the free balance hasn't - // changed and I don't we don't have an api from here to the unlocking chunks, or staking balance lock - } - - // TODO: setup a withdraw unbonded kill scenario, make variable over slashing spans - withdraw_unbonded_other { - let s in 0 .. MAX_SPANS; - clear_storage::(); - - // T::StakingInterface::add_slashing_spans(&stash, s); - - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(T::Currency::minimum_balance()); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond); - - // Add a new delegator - let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); - let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); - Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) - .unwrap(); - - // Sanity check join worked - assert_eq!( - T::StakingInterface::bonded_balance(&pool_account).unwrap( - - ), - min_create_bond + min_join_bond - ); - assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); - - // Unbond the new delegator - T::StakingInterface::set_current_era(0); - Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); - - // Sanity check that unbond worked - assert_eq!( - T::StakingInterface::bonded_balance(&pool_account).unwrap(), - min_create_bond - ); - - // Set the current era to ensure we can withdraw unbonded funds - T::StakingInterface::set_current_era(EraIndex::max_value()); - - // T::StakingInterface::add_slashing_spans(&pool_account, s); - whitelist_account!(joiner); - }: _(Origin::Signed(joiner.clone()), joiner.clone(), 0) - verify { - assert_eq!( - T::Currency::free_balance(&joiner), - min_join_bond * 2u32.into() - ); - } - - create { - clear_storage::(); - - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(T::Currency::minimum_balance()); - let depositor: T::AccountId = account("depositor", USER_SEED, 0); - - // Give the depositor some balance to bond - T::Currency::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); - - // Make sure no pools exist as a pre-condition for our verify checks - assert_eq!(RewardPools::::count(), 0); - assert_eq!(BondedPools::::count(), 0); - - whitelist_account!(depositor); - }: _( - Origin::Signed(depositor.clone()), - min_create_bond, - 0, - depositor.clone(), - depositor.clone(), - depositor.clone() - ) - verify { - assert_eq!(RewardPools::::count(), 1); - assert_eq!(BondedPools::::count(), 1); - let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); - assert_eq!( - new_pool, - BondedPoolStorage { - points: min_create_bond, - depositor: depositor.clone(), - root: depositor.clone(), - nominator: depositor.clone(), - state_toggler: depositor.clone(), - state: PoolState::Open, - } - ); - assert_eq!( - T::StakingInterface::bonded_balance(&pool_account), - Some(min_create_bond) - ); - } - - nominate { - clear_storage::(); - - // Create a pool - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(T::Currency::minimum_balance()); - 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 actual validators - let validators: Vec<_> = (0..T::StakingInterface::max_nominations()) - .map(|i| - T::Lookup::unlookup(account("stash", USER_SEED, i)) - ) - .collect(); - - whitelist_account!(depositor); - }:_(Origin::Signed(depositor.clone()), pool_account, validators) - verify { - assert_eq!(RewardPools::::count(), 1); - assert_eq!(BondedPools::::count(), 1); - let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); - assert_eq!( - new_pool, - BondedPoolStorage { - points: min_create_bond, - depositor: depositor.clone(), - root: depositor.clone(), - nominator: depositor.clone(), - state_toggler: depositor.clone(), - state: PoolState::Open, - } - ); - assert_eq!( - T::StakingInterface::bonded_balance(&pool_account), - Some(min_create_bond) - ); - } -} - -frame_benchmarking::impl_benchmark_test_suite!( - Pallet, - crate::mock::ExtBuilder::default().build(), - crate::mock::Runtime -); +// use super::*; +// use crate::Pallet as Pools; +// use frame_benchmarking::{account, vec, whitelist_account}; +// use frame_system::RawOrigin as Origin; + +// const USER_SEED: u32 = 0; +// const MAX_SPANS: u32 = 100; + +// fn clear_storage() { +// BondedPools::::remove_all(); +// RewardPools::::remove_all(); +// SubPoolsStorage::::remove_all(); +// Delegators::::remove_all(); +// } + +// fn create_funded_user_with_balance( +// string: &'static str, +// n: u32, +// balance: BalanceOf, +// ) -> T::AccountId { +// let user = account(string, n, USER_SEED); +// T::Currency::make_free_balance_be(&user, balance); +// user +// } + +// // Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free +// // balance. +// fn create_pool_account(n: u32, balance: BalanceOf) -> (T::AccountId, T::AccountId) { +// let pool_creator: T::AccountId = +// create_funded_user_with_balance::("pool_creator", n, balance * 2u32.into()); + +// Pools::::create( +// Origin::Signed(pool_creator.clone()).into(), +// balance, +// n as u16, +// pool_creator.clone(), +// pool_creator.clone(), +// pool_creator.clone(), +// ) +// .unwrap(); + +// let (pool_account, _) = BondedPools::::iter() +// .find(|(_, bonded_pool)| bonded_pool.depositor == pool_creator) +// .expect("pool_creator created a pool above"); + +// (pool_creator, pool_account) +// } + +// struct ListScenario { +// /// Stash/Controller that is expected to be moved. +// origin1: T::AccountId, +// dest_weight: BalanceOf, +// origin1_delegator: Option, +// } + +// impl ListScenario { +// /// An expensive scenario for bags-list implementation: +// /// +// /// - the node to be updated (r) is the head of a bag that has at least one other node. The bag +// /// itself will need to be read and written to update its head. The node pointed to by r.next +// /// will need to be read and written as it will need to have its prev pointer updated. Note +// /// that there are two other worst case scenarios for bag removal: 1) the node is a tail and +// /// 2) the node is a middle node with prev and next; all scenarios end up with the same number +// /// of storage reads and writes. +// /// +// /// - the destination bag has at least one node, which will need its next pointer updated. +// /// +// /// NOTE: while this scenario specifically targets a worst case for the bags-list, it should +// /// also elicit a worst case for other known `SortedListProvider` implementations; although +// /// this may not be true against unknown `SortedListProvider` implementations. +// pub(crate) fn new( +// origin_weight: BalanceOf, +// is_increase: bool, +// ) -> Result { +// ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); + +// if MaxPools::::get().unwrap_or(0) < 3 { +// // TODO: this is due to mock config, probably best to change +// MaxPools::::put(3); +// } + +// // Burn the entire issuance. +// // TODO: probably don't need this +// let i = T::Currency::burn(T::Currency::total_issuance()); +// sp_std::mem::forget(i); + +// // create accounts with the origin weight +// let (_, pool_origin1) = create_pool_account::(USER_SEED + 2, origin_weight); +// T::StakingInterface::nominate( +// pool_origin1.clone(), +// // NOTE: these don't really need to be validators. +// vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], +// )?; + +// let (_, pool_origin2) = create_pool_account::(USER_SEED + 3, origin_weight); +// T::StakingInterface::nominate( +// pool_origin2, +// vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))].clone(), +// )?; + +// // find a destination weight that will trigger the worst case scenario +// let dest_weight_as_vote = +// T::SortedListProvider::weight_update_worst_case(&pool_origin1, is_increase); + +// let dest_weight: BalanceOf = +// dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; + +// // create an account with the worst case destination weight +// let (_, pool_dest1) = create_pool_account::(USER_SEED + 1, dest_weight); +// T::StakingInterface::nominate( +// pool_dest1, +// vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], +// )?; + +// Ok(ListScenario { origin1: pool_origin1, dest_weight, origin1_delegator: None }) +// } + +// fn add_joiner(mut self, amount: BalanceOf) -> Self { +// let amount = MinJoinBond::::get() +// .max(T::Currency::minimum_balance()) +// // max the `given` amount with minimum thresholds for account balance and joining a pool +// // to ensure 1. the user can be created and 2. can join the pool +// .max(amount); + +// let joiner: T::AccountId = account("joiner", USER_SEED, 0); +// self.origin1_delegator = Some(joiner.clone()); +// T::Currency::make_free_balance_be(&joiner, amount * 2u32.into()); + +// let current_bonded = T::StakingInterface::bonded_balance(&self.origin1).unwrap(); + +// // Unbond `amount` from the underlying pool account so when the delegator joins +// // we will maintain `current_bonded` +// T::StakingInterface::unbond(self.origin1.clone(), amount).unwrap(); + +// // Account pool points for the unbonded balance +// BondedPools::::mutate(&self.origin1, |maybe_pool| { +// maybe_pool.as_mut().map(|pool| pool.points -= amount) +// }); + +// Pools::::join(Origin::Signed(joiner.clone()).into(), amount, self.origin1.clone()) +// .unwrap(); + +// assert_eq!(T::StakingInterface::bonded_balance(&self.origin1).unwrap(), current_bonded); + +// self +// } +// } + +// frame_benchmarking::benchmarks! { +// join { +// clear_storage::(); + +// let origin_weight = MinCreateBond::::get().max(T::Currency::minimum_balance()) * 2u32.into(); + +// // setup the worst case list scenario. +// let scenario = ListScenario::::new(origin_weight, true)?; +// assert_eq!( +// T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), +// origin_weight +// ); + + +// let max_additional = scenario.dest_weight.clone() - origin_weight; +// let joiner_free = T::Currency::minimum_balance() + max_additional; + +// let joiner: T::AccountId +// = create_funded_user_with_balance::("joiner", 0, joiner_free); + +// whitelist_account!(joiner); +// }: _(Origin::Signed(joiner.clone()), max_additional, scenario.origin1.clone()) +// verify { +// assert_eq!(T::Currency::free_balance(&joiner), joiner_free - max_additional); +// assert_eq!( +// T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), +// scenario.dest_weight +// ); +// } + +// claim_payout { +// clear_storage::(); + +// let origin_weight = MinCreateBond::::get().max(T::Currency::minimum_balance()) * 2u32.into(); +// let (depositor, pool_account) = create_pool_account::(0, origin_weight); + +// let reward_account = RewardPools::::get( +// pool_account +// ) +// .unwrap() +// .account; + +// // Send funds to the reward account of the pool +// T::Currency::make_free_balance_be(&reward_account, origin_weight); + +// // Sanity check +// assert_eq!( +// T::Currency::free_balance(&depositor), +// origin_weight +// ); + +// whitelist_account!(depositor); +// }:_(Origin::Signed(depositor.clone())) +// verify { +// assert_eq!( +// T::Currency::free_balance(&depositor), +// origin_weight * 2u32.into() +// ); +// assert_eq!( +// T::Currency::free_balance(&reward_account), +// Zero::zero() +// ); +// } + +// unbond_other { +// clear_storage::(); + +// // the weight the nominator will start at. The value used here is expected to be +// // significantly higher than the first position in a list (e.g. the first bag threshold). +// let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) +// .map_err(|_| "balance expected to be a u128") +// .unwrap(); +// let scenario = ListScenario::::new(origin_weight, false)?; + +// let amount = origin_weight - scenario.dest_weight.clone(); + +// let scenario = scenario.add_joiner(amount); + +// let delegator = scenario.origin1_delegator.unwrap().clone(); +// }: _(Origin::Signed(delegator.clone()), delegator.clone()) +// verify { +// assert!( +// T::StakingInterface::bonded_balance(&scenario.origin1).unwrap() +// <= scenario.dest_weight.clone() +// ); +// } + +// // TODO: setup a withdraw unbonded kill scenario +// pool_withdraw_unbonded { +// let s in 0 .. MAX_SPANS; +// clear_storage::(); + +// let min_create_bond = MinCreateBond::::get() +// .max(T::StakingInterface::minimum_bond()) +// .max(T::Currency::minimum_balance()); +// let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + +// // Add a new delegator +// let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); +// let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); +// Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) +// .unwrap(); + +// // Sanity check join worked +// assert_eq!( +// T::StakingInterface::bonded_balance(&pool_account).unwrap( + +// ), +// min_create_bond + min_join_bond +// ); +// assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); + +// // Unbond the new delegator +// Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + +// // Sanity check that unbond worked +// assert_eq!( +// T::StakingInterface::bonded_balance(&pool_account).unwrap(), +// min_create_bond +// ); +// // Set the current era +// T::StakingInterface::set_current_era(EraIndex::max_value()); + +// // T::StakingInterface::add_slashing_spans(&pool_account, s); +// whitelist_account!(pool_account); +// }: _(Origin::Signed(pool_account.clone()), pool_account.clone(), 2) +// verify { +// // The joiners funds didn't change +// assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); + +// // TODO: figure out if we can check anything else. Its tricky because the free balance hasn't +// // changed and I don't we don't have an api from here to the unlocking chunks, or staking balance lock +// } + +// // TODO: setup a withdraw unbonded kill scenario, make variable over slashing spans +// withdraw_unbonded_other { +// let s in 0 .. MAX_SPANS; +// clear_storage::(); + +// // T::StakingInterface::add_slashing_spans(&stash, s); + +// let min_create_bond = MinCreateBond::::get() +// .max(T::StakingInterface::minimum_bond()) +// .max(T::Currency::minimum_balance()); +// let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + +// // Add a new delegator +// let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); +// let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); +// Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) +// .unwrap(); + +// // Sanity check join worked +// assert_eq!( +// T::StakingInterface::bonded_balance(&pool_account).unwrap( + +// ), +// min_create_bond + min_join_bond +// ); +// assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); + +// // Unbond the new delegator +// T::StakingInterface::set_current_era(0); +// Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + +// // Sanity check that unbond worked +// assert_eq!( +// T::StakingInterface::bonded_balance(&pool_account).unwrap(), +// min_create_bond +// ); + +// // Set the current era to ensure we can withdraw unbonded funds +// T::StakingInterface::set_current_era(EraIndex::max_value()); + +// // T::StakingInterface::add_slashing_spans(&pool_account, s); +// whitelist_account!(joiner); +// }: _(Origin::Signed(joiner.clone()), joiner.clone(), 0) +// verify { +// assert_eq!( +// T::Currency::free_balance(&joiner), +// min_join_bond * 2u32.into() +// ); +// } + +// create { +// clear_storage::(); + +// let min_create_bond = MinCreateBond::::get() +// .max(T::StakingInterface::minimum_bond()) +// .max(T::Currency::minimum_balance()); +// let depositor: T::AccountId = account("depositor", USER_SEED, 0); + +// // Give the depositor some balance to bond +// T::Currency::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); + +// // Make sure no pools exist as a pre-condition for our verify checks +// assert_eq!(RewardPools::::count(), 0); +// assert_eq!(BondedPools::::count(), 0); + +// whitelist_account!(depositor); +// }: _( +// Origin::Signed(depositor.clone()), +// min_create_bond, +// 0, +// depositor.clone(), +// depositor.clone(), +// depositor.clone() +// ) +// verify { +// assert_eq!(RewardPools::::count(), 1); +// assert_eq!(BondedPools::::count(), 1); +// let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); +// assert_eq!( +// new_pool, +// BondedPoolStorage { +// points: min_create_bond, +// depositor: depositor.clone(), +// root: depositor.clone(), +// nominator: depositor.clone(), +// state_toggler: depositor.clone(), +// state: PoolState::Open, +// } +// ); +// assert_eq!( +// T::StakingInterface::bonded_balance(&pool_account), +// Some(min_create_bond) +// ); +// } + +// nominate { +// clear_storage::(); + +// // Create a pool +// let min_create_bond = MinCreateBond::::get() +// .max(T::StakingInterface::minimum_bond()) +// .max(T::Currency::minimum_balance()); +// 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 actual validators +// let validators: Vec<_> = (0..T::StakingInterface::max_nominations()) +// .map(|i| +// T::Lookup::unlookup(account("stash", USER_SEED, i)) +// ) +// .collect(); + +// whitelist_account!(depositor); +// }:_(Origin::Signed(depositor.clone()), pool_account, validators) +// verify { +// assert_eq!(RewardPools::::count(), 1); +// assert_eq!(BondedPools::::count(), 1); +// let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); +// assert_eq!( +// new_pool, +// BondedPoolStorage { +// points: min_create_bond, +// depositor: depositor.clone(), +// root: depositor.clone(), +// nominator: depositor.clone(), +// state_toggler: depositor.clone(), +// state: PoolState::Open, +// } +// ); +// assert_eq!( +// T::StakingInterface::bonded_balance(&pool_account), +// Some(min_create_bond) +// ); +// } +// } + +// frame_benchmarking::impl_benchmark_test_suite!( +// Pallet, +// crate::mock::ExtBuilder::default().build(), +// crate::mock::Runtime +// ); diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 12124199b421a..1bcc5b060a98d 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -350,17 +350,17 @@ fn balance_to_unbond( #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct Delegator { - pool: T::AccountId, + pub pool: T::AccountId, /// The quantity of points this delegator has in the bonded pool or in a sub pool if /// `Self::unbonding_era` is some. - points: BalanceOf, + pub points: BalanceOf, /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. /// Assuming no massive burning events, we expect this value to always be below total issuance. /// This value lines up with the `RewardPool::total_earnings` after a delegator claims a /// payout. - reward_pool_total_earnings: BalanceOf, + pub reward_pool_total_earnings: BalanceOf, /// The era this delegator started unbonding at. - unbonding_era: Option, + pub unbonding_era: Option, } /// All of a pool's possible states. @@ -1002,7 +1002,6 @@ pub mod pallet { // to unbond so we have the correct points for the balance:share ratio. bonded_pool.points = bonded_pool.points.saturating_sub(delegator.points); - // T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; // Unbond in the actual underlying pool T::StakingInterface::unbond(delegator.pool.clone(), balance_to_unbond)?; @@ -1309,7 +1308,7 @@ impl Pallet { // The new points that will be added to the pool. For every unit of balance that has // been earned by the reward pool, we inflate the reward pool points by // `bonded_pool.points`. In effect this allows each, single unit of balance (e.g. - // plank) to be divvied up pro-rata among delegators based on points. + // plank) to be divvied up pro rata among delegators based on points. let new_points = T::BalanceToU256::convert(bonded_pool.points).saturating_mul(new_earnings); // The points of the reward pool after taking into account the new earnings. Notice that diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index f148d16b44ef9..d143f135e92f9 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -49,7 +49,7 @@ type MaxNominators = <::BenchmarkingConfig as BenchmarkingConfig // Add slashing spans to a user account. Not relevant for actual use, only to benchmark // read and write operations. -pub(crate) fn add_slashing_spans(who: &T::AccountId, spans: u32) { +pub fn add_slashing_spans(who: &T::AccountId, spans: u32) { if spans == 0 { return } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index ed4957f0fe89e..a44507fa4fb7a 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1374,19 +1374,4 @@ impl StakingInterface for Pallet { fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult { Self::nominate(RawOrigin::Signed(controller).into(), targets) } - - #[cfg(feature = "runtime-benchmarks")] - fn max_nominations() -> u32 { - T::MaxNominations::get() - } - - #[cfg(feature = "runtime-benchmarks")] - fn weight_update_worst_case(who: &Self::AccountId, is_increase: bool) -> u64 { - T::SortedListProvider::weight_update_worst_case(who, is_increase) - } - - #[cfg(feature = "runtime-benchmarks")] - fn set_current_era(era: EraIndex) { - CurrentEra::::put(era) - } } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index d587b4f881910..c75e1a7082904 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -125,15 +125,4 @@ pub trait StakingInterface { stash: Self::AccountId, targets: sp_std::vec::Vec, ) -> DispatchResult; - - // Benchmarking helpers - - #[cfg(feature = "runtime-benchmarks")] - fn max_nominations() -> u32; - - #[cfg(feature = "runtime-benchmarks")] - fn weight_update_worst_case(who: &Self::AccountId, is_increase: bool) -> u64; - - #[cfg(feature = "runtime-benchmarks")] - fn set_current_era(era: EraIndex); } From 00d50b9363d5549b3c429fb32a4cf1c4729afedd Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 21 Feb 2022 23:02:30 -0800 Subject: [PATCH 122/299] Some more top level docs --- .../nomination-pools/benchmarking/src/lib.rs | 24 ++- frame/nomination-pools/src/benchmarking.rs | 13 +- frame/nomination-pools/src/lib.rs | 179 ++++++++++++------ frame/nomination-pools/src/mock.rs | 19 -- 4 files changed, 139 insertions(+), 96 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index a39a6499779e2..ab88a0f28a217 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -7,8 +7,9 @@ use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelis use frame_election_provider_support::SortedListProvider; use frame_support::ensure; use frame_system::RawOrigin as Origin; -use pallet_nomination_pools::MinCreateBond; -use pallet_nomination_pools::{BalanceOf, BondedPools, Delegators, MinJoinBond, Pallet as Pools}; +use pallet_nomination_pools::{ + BalanceOf, BondedPools, Delegators, MinCreateBond, MinJoinBond, Pallet as Pools, +}; use sp_runtime::traits::{StaticLookup, Zero}; use sp_staking::{EraIndex, StakingInterface}; @@ -79,8 +80,7 @@ struct ListScenario { origin1_delegator: Option, } -impl ListScenario { +impl ListScenario { /// An expensive scenario for bags-list implementation: /// /// - the node to be updated (r) is the head of a bag that has at least one other node. The bag @@ -343,11 +343,10 @@ frame_benchmarking::benchmarks! { let s in 0 .. MAX_SPANS; clear_storage::(); - // T::StakingInterface::add_slashing_spans(&stash, s); let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Add a new delegator @@ -372,18 +371,25 @@ frame_benchmarking::benchmarks! { T::StakingInterface::bonded_balance(&pool_account).unwrap(), min_create_bond ); + assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); // Set the current era to ensure we can withdraw unbonded funds pallet_staking::CurrentEra::::put(EraIndex::max_value()); - // T::StakingInterface::add_slashing_spans(&pool_account, s); + pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); whitelist_account!(joiner); - }: _(Origin::Signed(joiner.clone()), joiner.clone(), 0) + }: _(Origin::Signed(joiner.clone()), joiner.clone(), s) verify { assert_eq!( CurrencyOf::::free_balance(&joiner), min_join_bond * 2u32.into() ); + // The unlocking chunk was removed + assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 0); + assert_eq!( + SubPools::::get(&pool_account), + + ); } // create { diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs index 3d4e6e9f80397..1dab8c68306a4 100644 --- a/frame/nomination-pools/src/benchmarking.rs +++ b/frame/nomination-pools/src/benchmarking.rs @@ -25,8 +25,8 @@ // // Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free // // balance. -// fn create_pool_account(n: u32, balance: BalanceOf) -> (T::AccountId, T::AccountId) { -// let pool_creator: T::AccountId = +// fn create_pool_account(n: u32, balance: BalanceOf) -> (T::AccountId, T::AccountId) +// { let pool_creator: T::AccountId = // create_funded_user_with_balance::("pool_creator", n, balance * 2u32.into()); // Pools::::create( @@ -159,7 +159,6 @@ // origin_weight // ); - // let max_additional = scenario.dest_weight.clone() - origin_weight; // let joiner_free = T::Currency::minimum_balance() + max_additional; @@ -277,8 +276,8 @@ // assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); // // TODO: figure out if we can check anything else. Its tricky because the free balance hasn't -// // changed and I don't we don't have an api from here to the unlocking chunks, or staking balance lock -// } +// // changed and I don't we don't have an api from here to the unlocking chunks, or staking balance +// lock } // // TODO: setup a withdraw unbonded kill scenario, make variable over slashing spans // withdraw_unbonded_other { @@ -384,8 +383,8 @@ // .max(T::Currency::minimum_balance()); // 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 actual validators -// let validators: Vec<_> = (0..T::StakingInterface::max_nominations()) +// // Create some accounts to nominate. For the sake of benchmarking they don't need to be actual +// validators let validators: Vec<_> = (0..T::StakingInterface::max_nominations()) // .map(|i| // T::Lookup::unlookup(account("stash", USER_SEED, i)) // ) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 1bcc5b060a98d..c5770c096315e 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1,15 +1,14 @@ //! # Nomination Pools for Staking Delegation //! //! This pallet allows delegators to delegate their stake to nominating pools, each of which acts as -//! a nominator and nominates validators on their behalf. +//! a nominator and nominates validators on the delegators behalf. //! -//! ## Design +//! Index: +//! * [Key terms](#key-terms) +//! * [Usage](#usage) +//! * [Design](#design) //! -//! _Notes_: this section uses pseudo code to explain general design and does not necessarily -//! reflect the exact implementation. Additionally, a strong knowledge of `pallet-staking`'s api is -//! assumed. -//! -//! The delegation pool abstraction is composed of: +//! ## Key terms //! //! * bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and //! [`BondedPoolPoints`] @@ -18,15 +17,99 @@ //! * unbonding sub pools: Collection of pools at different phases of the unbonding lifecycle. See //! [`SubPools`] and [`SubPoolsStorage`]. //! * delegators: Accounts that are members of pools. See [`Delegator`] and [`Delegators`]. -// In order to maintain scalability, all operations are independent of the number of delegators. To -// do this, we store delegation specific information local to the delegator while the pool data -// structures have bounded datum. -// -// ### Design goals -// -// * Maintain integrity of slashing events, sufficiently penalizing delegators that where in the -// pool while it was backing a validator that got slashed. -// * Maximize scalability in terms of delegator count. +//! * point: A measure of a delegators portion of a pools funds. +//! +//! ## Usage +//! +//! ### Joining +//! +//! A account can stake funds with a nomination pool by calling [`Call::join`]. The amount to bond +//! is transferred from the delegator to the pools account. Note that an account can only belong to +//! one pool at a time. +//! +//! For more detailed docs see the [joining](#joining) section. +//! +//! ### Claiming rewards +//! +//! The delegator will earn rewards pro rata based on the delegators stake vs the sum of the +//! delegators in the pools stake. In order to claim their share of rewards a delegator must call +//! [`Call::claim_payout`]. A delegator can start claiming rewards in the era after they join. +//! Rewards can be claimed at any time and do not "expire". +//! +//! For more detailed docs see the [reward pool](#reward-pool) section. +//! +//! ### Leaving +//! +//! In order to leave, a delegator must take two steps. +//! +//! First, they must call [`Call::unbond_other`]. The unbond other extrinsic will start the +//! unbonding process by unbonding all of the delegators funds. Once +//! [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the delegator can call +//! [`Call::withdraw_unbonded_other`] to withdraw all there funds. +//! +//! For more detailed docs see the [bonded pool](#bonded-pool) and [unbonding sub +//! pools](#unbonding-sub-pools) sections. +//! +//! ### Slashes +//! +//! Slashes are distributed evenly across the bonded pool and the unbonding pools from slash era+1 +//! through the slash apply era. Thus, any delegator who either a) unbonded or b) was actively +//! bonded in the aforementioned range of eras will be affected by the slash. A delegator is slashed +//! pro-rata based on its stake relative to the total slash amount. +//! +//! For more detailed docs see the [slashing](#slashing) section. +//! +//! ### Adminstration +//! +//! A pool can be created with the [`Call::create`] call. Once created, the pools nominator or root +//! users must call [`Call::nominate`] to start nominating. [`Call::nominate`] can be called at +//! anytime to update validator selection. +//! +//! To help facilitate pool adminstration the pool has one of three states (see [`PoolState`]): +//! +//! * Open: Anyone can join the pool and no delegators can be permissionlessly removed. +//! * Blocked: No delegators can join and some admin roles can kick delegators. +//! * Destroying: No delegators can join and all delegators can be permissionlessly removed with +//! [`Call::unbond_other`] and [`Call::withdraw_unbonded_other`]. Once a pool is destroying state, +//! it cannot be reverted to another state. +//! +//! A pool has 3 administrative positions (see [`BondedPool`]): +//! +//! * Depositor: creates the pool and is the initial delegator. The can only leave pool once all +//! other delegators have left. Once they fully leave the pool is destroyed. +//! * Nominator: can select which validators the pool nominates. +//! * State-Toggler: can change the pools state and kick delegators if the pool is blocked. +//! * Root: can change the nominator, state-toggler, or itself and can perform any of the actions +//! the nominator or state-toggler can. +//! +//! Note: if it is desired that any of the admin roles are not accessible, they can be set to an +//! anonymous proxy account that has no proxies (and is thus provably keyless). +//! +//! **Relevant extrinsics:** +//! +//! * [`Call::create`] +//! * [`Call::nominate`] +//! * [`Call::unbond_other`] +//! * [`Call::withdraw_unbonded_other`] +//! +//! ## Design +//! +//! _Notes_: this section uses pseudo code to explain general design and does not necessarily +//! reflect the exact implementation. Additionally, a working knowledge of `pallet-staking`'s api is +//! assumed. +//! In order to maintain scalability, all operations are independent of the number of delegators. To +//! do this, we store delegation specific information local to the delegator while the pool data +//! structures have bounded datum. +//! +//! ### Goals +//! +//! * Maintain network security by upholding integrity of slashing events, sufficiently penalizing +//! delegators that where in the pool while it was backing a validator that got slashed. +//! * Maximize scalability in terms of delegator count. +//! +//! In order to maintain scalability, all operations are independent of the number of delegators. To +//! do this, delegation specific information is stored local to the delegator while the pool data +//! structures have bounded datum. //! //! ### Bonded pool //! @@ -130,6 +213,7 @@ //! rewards for the active era despite not contributing to the pool's vote weight. If it joins //! after the election snapshot is taken it will benefit from the rewards of the next _2_ eras //! because it's vote weight will not be counted until the election snapshot in active era + 1. +//! Related: https://github.com/paritytech/substrate/issues/10861 //! //! **Relevant extrinsics:** //! @@ -182,57 +266,30 @@ //! //! ### Slashing //! -//! Slashes are distributed evenly across the bonded pool and the unbonding pools from slash era+1 -//! through the slash apply era. -// -// Slashes are computed and executed by: -// -// 1) Balances of the bonded pool and the unbonding pools in range `slash_era + -// 1..=apply_era` are summed and stored in `total_balance_affected`. -// 2) `slash_ratio` is computed as `slash_amount / total_balance_affected`. -// 3) `bonded_pool_balance_after_slash`is computed as `(1- slash_ratio) * bonded_pool_balance`. -// 4) For all `unbonding_pool` in range `slash_era + 1..=apply_era` set their balance to `(1 - -// slash_ratio) * unbonding_pool_balance`. -// -// Unbonding pools need to be slashed to ensure all nominators whom where in the bonded pool -// while it was backing a validator that equivocated are punished. Without these measures a -// nominator could unbond right after a validator equivocated with no consequences. -// -// This strategy is unfair to delegators who joined after the slash, because they get slashed as -// well, but spares delegators who unbond. The latter is much more important for security: if a -// pool's validators are attacking the network, their delegators need to unbond fast! Avoiding -// slashes gives them an incentive to do that if validators get repeatedly slashed. -// -// To be fair to joiners, this implementation also need joining pools, which are actively staking, -// in addition to the unbonding pools. For maintenance simplicity these are not implemented. -//! -//! ### Pool administration +//! Slashes are computed and executed by: //! -//! To help facilitate pool adminstration the pool has one of three states (see [`PoolState`]): -//! -//! * Open: Anyone can join the pool and no delegators can be permissionlessly removed. -//! * Blocked: No delegators can join and some admin roles can kick delegators. -//! * Destroying: No delegators can join and all delegators can be permissionlessly removed. Once a -//! pool is destroying state, it cannot be reverted to another state. +//! 1) Balances of the bonded pool and the unbonding pools in range `slash_era + +//! 1..=apply_era` are summed and stored in `total_balance_affected`. +//! 2) `slash_ratio` is computed as `slash_amount / total_balance_affected`. +//! 3) `bonded_pool_balance_after_slash`is computed as `(1- slash_ratio) * bonded_pool_balance`. +//! 4) For all `unbonding_pool` in range `slash_era + 1..=apply_era` set their balance to `(1 - +//! slash_ratio) * unbonding_pool_balance`. //! -//! A pool has 3 administrative positions (see [`BondedPool`]): +//! Unbonding pools need to be slashed to ensure all nominators whom where in the bonded pool +//! while it was backing a validator that equivocated are punished. Without these measures a +//! nominator could unbond right after a validator equivocated with no consequences. //! -//! * Depositor: creates the pool and is the initial delegator. The can only leave pool once all -//! other delegators have left. Once they fully leave the pool is destroyed. -//! * Nominator: can select which validators the pool nominates. -//! * State-Toggler: can change the pools state and kick delegators if the pool is blocked. -//! * Root: can change the nominator, state-toggler, or itself and can perform any of the actions -//! the nominator or state-toggler can. +//! This strategy is unfair to delegators who joined after the slash, because they get slashed as +//! well, but spares delegators who unbond. The latter is much more important for security: if a +//! pool's validators are attacking the network, their delegators need to unbond fast! Avoiding +//! slashes gives them an incentive to do that if validators get repeatedly slashed. //! -//! Note: if it is desired that any of the admin roles are not accessible, they can be set to an -//! anonymous proxy account that has no proxies (and is thus provably keyless). +//! To be fair to joiners, this implementation also need joining pools, which are actively staking, +//! in addition to the unbonding pools. For maintenance simplicity these are not implemented. Related: https://github.com/paritytech/substrate/issues/10860 //! -//! **Relevant extrinsics:** +//! **Relevant methods:** //! -//! * [`Call::create`] -//! * [`Call::nominate`] -//! * [`Call::unbond_other`] -//! * [`Call::withdraw_unbonded_other`] +//! * [`Pallet::do_slash`] //! //! ### Limitations //! diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index c297c18ed0d10..9863ec9292c96 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -81,25 +81,6 @@ impl sp_staking::StakingInterface for StakingMock { Nominations::set(nominations); Ok(()) } - - fn max_nominations() -> u32 { - 3 - } - - fn weight_update_worst_case(_: &Self::AccountId, is_increase: bool) -> u64 { - if is_increase { - u64::MAX / 2 - } else { - MinCreateBond::::get() - .max(StakingMock::minimum_bond()) - .try_into() - .unwrap() - } - } - - fn set_current_era(era: EraIndex) { - CurrentEra::set(era); - } } impl frame_system::Config for Runtime { From 3ad4313d20055d405353e4996dc7236f57dcffb3 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Feb 2022 13:37:01 -0800 Subject: [PATCH 123/299] Finish tranistion benchmarks to crate --- .../nomination-pools/benchmarking/src/lib.rs | 171 +++++++++--------- frame/nomination-pools/src/lib.rs | 22 +-- 2 files changed, 96 insertions(+), 97 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index ab88a0f28a217..e971832020dad 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -8,10 +8,13 @@ use frame_election_provider_support::SortedListProvider; use frame_support::ensure; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ - BalanceOf, BondedPools, Delegators, MinCreateBond, MinJoinBond, Pallet as Pools, + BalanceOf, BondedPoolStorage, BondedPools, Delegators, MinCreateBond, MinJoinBond, + Pallet as Pools, PoolState, RewardPools, SubPools, SubPoolsStorage, SubPoolsWithEra, + UnbondPool, }; use sp_runtime::traits::{StaticLookup, Zero}; use sp_staking::{EraIndex, StakingInterface}; +use frame_support::traits::Get; // `frame_benchmarking::benchmarks!` macro code needs this use pallet_nomination_pools::Call; @@ -386,95 +389,91 @@ frame_benchmarking::benchmarks! { ); // The unlocking chunk was removed assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 0); - assert_eq!( - SubPools::::get(&pool_account), + } + + create { + clear_storage::(); + + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + let depositor: T::AccountId = account("depositor", USER_SEED, 0); + // 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 + assert_eq!(RewardPools::::count(), 0); + assert_eq!(BondedPools::::count(), 0); + + whitelist_account!(depositor); + }: _( + Origin::Signed(depositor.clone()), + min_create_bond, + 0, + depositor.clone(), + depositor.clone(), + depositor.clone() + ) + verify { + assert_eq!(RewardPools::::count(), 1); + assert_eq!(BondedPools::::count(), 1); + let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); + assert_eq!( + new_pool, + BondedPoolStorage { + points: min_create_bond, + depositor: depositor.clone(), + root: depositor.clone(), + nominator: depositor.clone(), + state_toggler: depositor.clone(), + state: PoolState::Open, + } + ); + assert_eq!( + T::StakingInterface::bonded_balance(&pool_account), + Some(min_create_bond) ); } -// create { -// clear_storage::(); - -// let min_create_bond = MinCreateBond::::get() -// .max(T::StakingInterface::minimum_bond()) -// .max(T::Currency::minimum_balance()); -// let depositor: T::AccountId = account("depositor", USER_SEED, 0); - -// // Give the depositor some balance to bond -// T::Currency::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); - -// // Make sure no pools exist as a pre-condition for our verify checks -// assert_eq!(RewardPools::::count(), 0); -// assert_eq!(BondedPools::::count(), 0); - -// whitelist_account!(depositor); -// }: _( -// Origin::Signed(depositor.clone()), -// min_create_bond, -// 0, -// depositor.clone(), -// depositor.clone(), -// depositor.clone() -// ) -// verify { -// assert_eq!(RewardPools::::count(), 1); -// assert_eq!(BondedPools::::count(), 1); -// let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); -// assert_eq!( -// new_pool, -// BondedPoolStorage { -// points: min_create_bond, -// depositor: depositor.clone(), -// root: depositor.clone(), -// nominator: depositor.clone(), -// state_toggler: depositor.clone(), -// state: PoolState::Open, -// } -// ); -// assert_eq!( -// T::StakingInterface::bonded_balance(&pool_account), -// Some(min_create_bond) -// ); -// } - -// nominate { -// clear_storage::(); - -// // Create a pool -// let min_create_bond = MinCreateBond::::get() -// .max(T::StakingInterface::minimum_bond()) -// .max(T::Currency::minimum_balance()); -// 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 actual -// validators let validators: Vec<_> = (0..T::MaxNominations::get()) -// .map(|i| -// T::Lookup::unlookup(account("stash", USER_SEED, i)) -// ) -// .collect(); - -// whitelist_account!(depositor); -// }:_(Origin::Signed(depositor.clone()), pool_account, validators) -// verify { -// assert_eq!(RewardPools::::count(), 1); -// assert_eq!(BondedPools::::count(), 1); -// let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); -// assert_eq!( -// new_pool, -// BondedPoolStorage { -// points: min_create_bond, -// depositor: depositor.clone(), -// root: depositor.clone(), -// nominator: depositor.clone(), -// state_toggler: depositor.clone(), -// state: PoolState::Open, -// } -// ); -// assert_eq!( -// T::StakingInterface::bonded_balance(&pool_account), -// Some(min_create_bond) -// ); -// } + nominate { + clear_storage::(); + + // Create a pool + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + 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 actual validators + let validators: Vec<_> = (0..T::MaxNominations::get()) + .map(|i| + T::Lookup::unlookup(account("stash", USER_SEED, i)) + ) + .collect(); + + whitelist_account!(depositor); + }:_(Origin::Signed(depositor.clone()), pool_account, validators) + verify { + assert_eq!(RewardPools::::count(), 1); + assert_eq!(BondedPools::::count(), 1); + let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); + assert_eq!( + new_pool, + BondedPoolStorage { + points: min_create_bond, + depositor: depositor.clone(), + root: depositor.clone(), + nominator: depositor.clone(), + state_toggler: depositor.clone(), + state: PoolState::Open, + } + ); + assert_eq!( + T::StakingInterface::bonded_balance(&pool_account), + Some(min_create_bond) + ); + } } frame_benchmarking::impl_benchmark_test_suite!( diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index c5770c096315e..6be6f05def00e 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -24,8 +24,8 @@ //! ### Joining //! //! A account can stake funds with a nomination pool by calling [`Call::join`]. The amount to bond -//! is transferred from the delegator to the pools account. Note that an account can only belong to -//! one pool at a time. +//! is transferred from the delegator to the pools account and immediately increases the pools bond. +//! Note that an account can only belong to one pool at a time. //! //! For more detailed docs see the [joining](#joining) section. //! @@ -33,8 +33,8 @@ //! //! The delegator will earn rewards pro rata based on the delegators stake vs the sum of the //! delegators in the pools stake. In order to claim their share of rewards a delegator must call -//! [`Call::claim_payout`]. A delegator can start claiming rewards in the era after they join. -//! Rewards can be claimed at any time and do not "expire". +//! [`Call::claim_payout`]. A delegator can start claiming rewards in the era after they join; +//! rewards can be claimed periodically at any time after that and do not "expire". //! //! For more detailed docs see the [reward pool](#reward-pool) section. //! @@ -43,9 +43,10 @@ //! In order to leave, a delegator must take two steps. //! //! First, they must call [`Call::unbond_other`]. The unbond other extrinsic will start the -//! unbonding process by unbonding all of the delegators funds. Once -//! [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the delegator can call -//! [`Call::withdraw_unbonded_other`] to withdraw all there funds. +//! unbonding process by unbonding all of the delegators funds. +//! +//! Second, Once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the delegator +//! can call [`Call::withdraw_unbonded_other`] to withdraw all there funds. //! //! For more detailed docs see the [bonded pool](#bonded-pool) and [unbonding sub //! pools](#unbonding-sub-pools) sections. @@ -62,7 +63,7 @@ //! ### Adminstration //! //! A pool can be created with the [`Call::create`] call. Once created, the pools nominator or root -//! users must call [`Call::nominate`] to start nominating. [`Call::nominate`] can be called at +//! user must call [`Call::nominate`] to start nominating. [`Call::nominate`] can be called at //! anytime to update validator selection. //! //! To help facilitate pool adminstration the pool has one of three states (see [`PoolState`]): @@ -313,7 +314,6 @@ // a pool is flipped to a destroying state it cannot change its state. // TODO -// - Write user top level docs and make the design docs internal // - Refactor staking slashing to always slash unlocking chunks (then back port) // - backport making ledger generic over ^^ IDEA: maybe staking can slash unlocking chunks, and then // pools is passed the updated unlocking chunks and makes updates based on that @@ -352,7 +352,7 @@ pub use weights::WeightInfo; pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -type SubPoolsWithEra = BoundedBTreeMap, TotalUnbondingPools>; +pub type SubPoolsWithEra = BoundedBTreeMap, TotalUnbondingPools>; // NOTE: this assumes the balance type u128 or smaller. type RewardPoints = U256; @@ -420,7 +420,7 @@ pub struct Delegator { pub unbonding_era: Option, } -/// All of a pool's possible states. +/// A pool's possible states. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone))] pub enum PoolState { From 8f4dffc46ebe6a8328fdf8c532dacd188975a8a2 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Feb 2022 14:36:38 -0800 Subject: [PATCH 124/299] Hook up benchmark pallet to node runtime --- Cargo.lock | 6 ++--- bin/node/runtime/Cargo.toml | 3 ++- bin/node/runtime/src/lib.rs | 26 +++++++++++-------- frame/nomination-pools/Cargo.toml | 12 --------- .../nomination-pools/benchmarking/Cargo.toml | 10 +++---- .../nomination-pools/benchmarking/src/lib.rs | 10 ++++--- .../nomination-pools/benchmarking/src/mock.rs | 1 - frame/nomination-pools/src/lib.rs | 1 + 8 files changed, 31 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cccc986a9a14e..afa5188d4d159 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4894,6 +4894,7 @@ dependencies = [ "pallet-mmr", "pallet-multisig", "pallet-nomination-pools", + "pallet-nomination-pools-benchmarking", "pallet-offences", "pallet-offences-benchmarking", "pallet-preimage", @@ -6049,10 +6050,8 @@ dependencies = [ name = "pallet-nomination-pools" version = "1.0.0" dependencies = [ - "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", "pallet-balances", "parity-scale-codec", "scale-info", @@ -6066,7 +6065,7 @@ dependencies = [ [[package]] name = "pallet-nomination-pools-benchmarking" -version = "4.0.0" +version = "1.0.0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -6084,7 +6083,6 @@ dependencies = [ "sp-io", "sp-runtime", "sp-staking", - "sp-tracing", ] [[package]] diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 7269a1b13106a..72c1bb3263eb8 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -77,6 +77,7 @@ pallet-membership = { version = "4.0.0-dev", default-features = false, path = ". pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" } pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"} +pallet-nomination-pools-benchmarking = { version = "1.0.0", default-features = false, optional = true, path = "../../../frame/nomination-pools/benchmarking" } pallet-offences = { version = "4.0.0-dev", default-features = false, path = "../../../frame/offences" } pallet-offences-benchmarking = { version = "4.0.0-dev", path = "../../../frame/offences/benchmarking", default-features = false, optional = true } pallet-preimage = { version = "4.0.0-dev", default-features = false, path = "../../../frame/preimage" } @@ -207,7 +208,7 @@ runtime-benchmarks = [ "pallet-membership/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", - "pallet-nomination-pools/runtime-benchmarks", + "pallet-nomination-pools-benchmarking", "pallet-offences-benchmarking", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 72985024e1c1f..0fa16d7ecab89 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -286,17 +286,18 @@ impl InstanceFilter for ProxyType { ProxyType::Any => true, ProxyType::NonTransfer => !matches!( c, - Call::Balances(..) | - Call::Assets(..) | Call::Uniques(..) | - Call::Vesting(pallet_vesting::Call::vested_transfer { .. }) | - Call::Indices(pallet_indices::Call::transfer { .. }) + Call::Balances(..) + | Call::Assets(..) | Call::Uniques(..) + | Call::Vesting(pallet_vesting::Call::vested_transfer { .. }) + | Call::Indices(pallet_indices::Call::transfer { .. }) ), ProxyType::Governance => matches!( c, - Call::Democracy(..) | - Call::Council(..) | Call::Society(..) | - Call::TechnicalCommittee(..) | - Call::Elections(..) | Call::Treasury(..) + Call::Democracy(..) + | Call::Council(..) | Call::Society(..) + | Call::TechnicalCommittee(..) + | Call::Elections(..) + | Call::Treasury(..) ), ProxyType::Staking => matches!(c, Call::Staking(..)), } @@ -631,8 +632,8 @@ impl frame_support::pallet_prelude::Get { let seed = sp_io::offchain::random_seed(); let random = ::decode(&mut TrailingZeroInput::new(&seed)) - .expect("input is padded with zeroes; qed") % - max.saturating_add(1); + .expect("input is padded with zeroes; qed") + % max.saturating_add(1); random as usize }, }; @@ -1524,7 +1525,7 @@ mod benches { [pallet_membership, TechnicalMembership] [pallet_mmr, Mmr] [pallet_multisig, Multisig] - [pallet_nomination_pools, NominationPools] + [pallet_nomination_pools, NominationPoolsBench::] [pallet_offences, OffencesBench::] [pallet_preimage, Preimage] [pallet_proxy, Proxy] @@ -1831,6 +1832,7 @@ impl_runtime_apis! { use pallet_offences_benchmarking::Pallet as OffencesBench; use frame_system_benchmarking::Pallet as SystemBench; use baseline::Pallet as BaselineBench; + use pallet_nomination_pools_benchmarking::Pallet as NominationPoolsBench; let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -1852,11 +1854,13 @@ impl_runtime_apis! { use pallet_offences_benchmarking::Pallet as OffencesBench; use frame_system_benchmarking::Pallet as SystemBench; use baseline::Pallet as BaselineBench; + use pallet_nomination_pools_benchmarking::Pallet as NominationPoolsBench; impl pallet_session_benchmarking::Config for Runtime {} impl pallet_offences_benchmarking::Config for Runtime {} impl frame_system_benchmarking::Config for Runtime {} impl baseline::Config for Runtime {} + impl pallet_nomination_pools_benchmarking::Config for Runtime {} let whitelist: Vec = vec![ // Block Number diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index d3d3a92e4e3c6..9db5d529b7f52 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -25,17 +25,9 @@ sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../pr sp-core = { version = "5.0.0", default-features = false, path = "../../primitives/core" } sp-io = { version = "5.0.0", default-features = false, path = "../../primitives/io" } -# third party -log = { version = "0.4.14", default-features = false } - -# Optional imports for benchmarking -frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking", optional = true, default-features = false } - [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } sp-tracing = { version = "4.0.0", path = "../../primitives/tracing" } -sp-io = { version = "5.0.0", path = "../../primitives/io" } - [features] default = ["std"] @@ -48,7 +40,3 @@ std = [ "frame-support/std", "frame-system/std", ] -runtime-benchmarks = [ - "frame-benchmarking", - "sp-staking/runtime-benchmarks" -] diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index ef49f66fe891e..e2a1c6622320a 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-nomination-pools-benchmarking" -version = "4.0.0" +version = "1.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -22,17 +22,14 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = " frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../election-provider-support" } 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-bags-list = { version = "4.0.0-dev", features = ["runtime-benchmarks"], path = "../../bags-list" } +pallet-bags-list = { version = "4.0.0-dev", default-features = false, features = ["runtime-benchmarks"], path = "../../bags-list" } pallet-staking = { version = "4.0.0-dev", default-features = false, features = ["runtime-benchmarks"], path = "../../staking" } -pallet-nomination-pools = { version = "1.0.0", default-features = false, features = ["runtime-benchmarks"], path = "../" } +pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../" } # Substrate Primitives sp-runtime = { version = "5.0.0", default-features = false, path = "../../../primitives/runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } -# TODO: remove -sp-tracing = { version = "4.0.0", path = "../../../primitives/tracing" } - [dev-dependencies] pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } @@ -49,6 +46,7 @@ std = [ "frame-system/std", "frame-election-provider-support/std", "pallet-balances/std", + "pallet-bags-list/std", "pallet-staking/std", "pallet-nomination-pools/std", "sp-runtime/std", diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index e971832020dad..8c424b6af5852 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -1,11 +1,15 @@ //! Benchmarks for the nomination pools coupled with the staking and bags list pallets. +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + #[cfg(test)] mod mock; use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelist_account}; use frame_election_provider_support::SortedListProvider; -use frame_support::ensure; +use frame_support::{ensure, traits::Get}; +use frame_benchmarking::Vec; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ BalanceOf, BondedPoolStorage, BondedPools, Delegators, MinCreateBond, MinJoinBond, @@ -14,7 +18,6 @@ use pallet_nomination_pools::{ }; use sp_runtime::traits::{StaticLookup, Zero}; use sp_staking::{EraIndex, StakingInterface}; -use frame_support::traits::Get; // `frame_benchmarking::benchmarks!` macro code needs this use pallet_nomination_pools::Call; @@ -445,7 +448,8 @@ frame_benchmarking::benchmarks! { .max(CurrencyOf::::minimum_balance()); 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 actual validators + // Create some accounts to nominate. For the sake of benchmarking they don't need to be + // actual validators let validators: Vec<_> = (0..T::MaxNominations::get()) .map(|i| T::Lookup::unlookup(account("stash", USER_SEED, i)) diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 4582df333f631..b5aa0727d8cf6 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -180,7 +180,6 @@ frame_support::construct_runtime!( ); 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, diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 6be6f05def00e..63ffb94176266 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -322,6 +322,7 @@ // - staking provider current era should not return option, just era index // - write detailed docs for StakingInterface +// Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ From ed1936f80bb5cf780fb0ffa0bdc8e1610f6fb8b9 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Feb 2022 16:51:53 -0800 Subject: [PATCH 125/299] Get benches to work with node runtime --- Cargo.lock | 2 + bin/node/runtime/src/lib.rs | 21 ++++--- .../nomination-pools/benchmarking/Cargo.toml | 3 + .../nomination-pools/benchmarking/src/lib.rs | 59 ++++++++----------- 4 files changed, 40 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index afa5188d4d159..9675296d03f30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6071,6 +6071,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", + "log 0.4.14", "pallet-bags-list", "pallet-balances", "pallet-nomination-pools", @@ -6083,6 +6084,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-staking", + "sp-std", ] [[package]] diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0fa16d7ecab89..a562c242f5caa 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -286,18 +286,17 @@ impl InstanceFilter for ProxyType { ProxyType::Any => true, ProxyType::NonTransfer => !matches!( c, - Call::Balances(..) - | Call::Assets(..) | Call::Uniques(..) - | Call::Vesting(pallet_vesting::Call::vested_transfer { .. }) - | Call::Indices(pallet_indices::Call::transfer { .. }) + Call::Balances(..) | + Call::Assets(..) | Call::Uniques(..) | + Call::Vesting(pallet_vesting::Call::vested_transfer { .. }) | + Call::Indices(pallet_indices::Call::transfer { .. }) ), ProxyType::Governance => matches!( c, - Call::Democracy(..) - | Call::Council(..) | Call::Society(..) - | Call::TechnicalCommittee(..) - | Call::Elections(..) - | Call::Treasury(..) + Call::Democracy(..) | + Call::Council(..) | Call::Society(..) | + Call::TechnicalCommittee(..) | + Call::Elections(..) | Call::Treasury(..) ), ProxyType::Staking => matches!(c, Call::Staking(..)), } @@ -632,8 +631,8 @@ impl frame_support::pallet_prelude::Get { let seed = sp_io::offchain::random_seed(); let random = ::decode(&mut TrailingZeroInput::new(&seed)) - .expect("input is padded with zeroes; qed") - % max.saturating_add(1); + .expect("input is padded with zeroes; qed") % + max.saturating_add(1); random as usize }, }; diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index e2a1c6622320a..b69a64312693d 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -29,6 +29,9 @@ pallet-nomination-pools = { version = "1.0.0", default-features = false, path = # Substrate Primitives sp-runtime = { version = "5.0.0", default-features = false, path = "../../../primitives/runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } +sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } + +log = { version = "0.4.14", default-features = false } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 8c424b6af5852..ec00c4750f2c1 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -6,10 +6,9 @@ #[cfg(test)] mod mock; -use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelist_account}; +use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelist_account, Vec}; use frame_election_provider_support::SortedListProvider; use frame_support::{ensure, traits::Get}; -use frame_benchmarking::Vec; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ BalanceOf, BondedPoolStorage, BondedPools, Delegators, MinCreateBond, MinJoinBond, @@ -79,6 +78,12 @@ fn create_pool_account( (pool_creator, pool_account) } +fn vote_to_balance( + vote: u64, +) -> Result, &'static str> { + vote.try_into().map_err(|_| "could not convert u64 to Balance") +} + struct ListScenario { /// Stash/Controller that is expected to be moved. origin1: T::AccountId, @@ -113,9 +118,8 @@ impl ListScenario { ); // Burn the entire issuance. - // TODO: probably don't need this - // let i = T::Currency::burn(T::Currency::total_issuance()); - // sp_std::mem::forget(i); + let i = CurrencyOf::::burn(CurrencyOf::::total_issuance()); + sp_std::mem::forget(i); // create accounts with the origin weight let (_, pool_origin1) = create_pool_account::(USER_SEED + 2, origin_weight); @@ -127,7 +131,7 @@ impl ListScenario { let (_, pool_origin2) = create_pool_account::(USER_SEED + 3, origin_weight); T::StakingInterface::nominate( - pool_origin2, + pool_origin2.clone(), vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))].clone(), )?; @@ -144,10 +148,15 @@ impl ListScenario { // Create an account with the worst case destination weight let (_, pool_dest1) = create_pool_account::(USER_SEED + 1, dest_weight); T::StakingInterface::nominate( - pool_dest1, + pool_dest1.clone(), vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], )?; + let weight_of = pallet_staking::Pallet::::weight_of_fn(); + assert_eq!(vote_to_balance::(weight_of(&pool_origin1)).unwrap(), origin_weight); + assert_eq!(vote_to_balance::(weight_of(&pool_origin2)).unwrap(), origin_weight); + assert_eq!(vote_to_balance::(weight_of(&pool_dest1)).unwrap(), dest_weight); + Ok(ListScenario { origin1: pool_origin1, dest_weight, origin1_delegator: None }) } @@ -177,7 +186,14 @@ impl ListScenario { Pools::::join(Origin::Signed(joiner.clone()).into(), amount, self.origin1.clone()) .unwrap(); - assert_eq!(T::StakingInterface::bonded_balance(&self.origin1).unwrap(), original_bonded); + // Sanity check that the vote weight is still the same as the original bonded + let weight_of = pallet_staking::Pallet::::weight_of_fn(); + assert_eq!(vote_to_balance::(weight_of(&self.origin1)).unwrap(), original_bonded); + + // Sanity check the delegator was added correctly + let delegator = Delegators::::get(&joiner).unwrap(); + assert_eq!(delegator.points, amount); + assert_eq!(delegator.pool, self.origin1); self } @@ -260,32 +276,7 @@ frame_benchmarking::benchmarks! { let scenario = scenario.add_joiner(amount); let delegator_id = scenario.origin1_delegator.unwrap().clone(); - - // Sanity check the delegator was added correctly - let delegator = Delegators::::get( - &delegator_id - ) - .unwrap(); - assert_eq!( - delegator.points, - amount - ); - assert_eq!( - delegator.pool, - scenario.origin1 - ); - // Sanity check the pool maintained the origin weight - let bonded_pool = BondedPools::::get(scenario.origin1.clone()).unwrap(); - assert_eq!( - bonded_pool.points, - origin_weight - ); - let bonded_before = T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(); - assert_eq!( - bonded_before, - origin_weight - ); - + whitelist_account!(delegator_id); }: _(Origin::Signed(delegator_id.clone()), delegator_id.clone()) verify { let bonded_after = T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(); From a5b52c8c94f9b1a2a6917327397d956b490bca01 Mon Sep 17 00:00:00 2001 From: Parity Bot Date: Wed, 23 Feb 2022 01:13:21 +0000 Subject: [PATCH 126/299] cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_nomination_pools --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/nomination-pools/src/weights.rs --template=./.maintain/frame-weight-template.hbs --- frame/nomination-pools/src/weights.rs | 270 ++++++++++++++++++++++++-- 1 file changed, 257 insertions(+), 13 deletions(-) diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index 126ab600f3fbf..0054490532c33 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -1,37 +1,281 @@ -use frame_support::weights::Weight; +// This file is part of Substrate. -/// Weight functions needed for pallet_bags_list. +// 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_nomination_pools +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-02-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/substrate +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_nomination_pools +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/nomination-pools/src/weights.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_nomination_pools. pub trait WeightInfo { fn join() -> Weight; fn claim_payout() -> Weight; fn unbond_other() -> Weight; - fn pool_withdraw_unbonded(s: u32) -> Weight; - fn withdraw_unbonded_other(s: u32) -> Weight; + fn pool_withdraw_unbonded(s: u32, ) -> Weight; + fn withdraw_unbonded_other(s: u32, ) -> Weight; fn create() -> Weight; fn nominate() -> Weight; } +/// Weights for pallet_nomination_pools using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: NominationPools MinJoinBond (r:1 w:0) + // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:0) + // Storage: System Account (r:2 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:3 w:3) + // Storage: BagsList ListBags (r:2 w:2) + // Storage: NominationPools CounterForDelegators (r:1 w:1) + fn join() -> Weight { + (109_762_000 as Weight) + .saturating_add(T::DbWeight::get().reads(15 as Weight)) + .saturating_add(T::DbWeight::get().writes(11 as Weight)) + } + // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn claim_payout() -> Weight { + (45_848_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:2 w:1) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:3 w:3) + // Storage: Staking Bonded (r:1 w:0) + // Storage: BagsList ListBags (r:2 w:2) + // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + fn unbond_other() -> Weight { + (114_657_000 as Weight) + .saturating_add(T::DbWeight::get().reads(18 as Weight)) + .saturating_add(T::DbWeight::get().writes(13 as Weight)) + } + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + fn pool_withdraw_unbonded(s: u32, ) -> Weight { + (33_087_000 as Weight) + // Standard Error: 0 + .saturating_add((50_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: NominationPools Delegators (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: NominationPools CounterForDelegators (r:1 w:1) + fn withdraw_unbonded_other(s: u32, ) -> Weight { + (66_522_000 as Weight) + // Standard Error: 0 + .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(T::DbWeight::get().reads(8 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) + } + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: NominationPools MinCreateBond (r:1 w:0) + // Storage: NominationPools MaxPools (r:1 w:0) + // Storage: NominationPools CounterForBondedPools (r:1 w:1) + // Storage: NominationPools Delegators (r:1 w:1) + // Storage: System ParentHash (r:1 w:0) + // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking HistoryDepth (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: NominationPools CounterForDelegators (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: Staking Payee (r:0 w:1) + fn create() -> Weight { + (92_839_000 as Weight) + .saturating_add(T::DbWeight::get().reads(17 as Weight)) + .saturating_add(T::DbWeight::get().writes(11 as Weight)) + } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking MaxNominatorsCount (r:1 w:0) + // Storage: Staking Validators (r:17 w:0) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: BagsList ListNodes (r:1 w:1) + // Storage: BagsList ListBags (r:1 w:1) + // Storage: BagsList CounterForListNodes (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + fn nominate() -> Weight { + (79_587_000 as Weight) + .saturating_add(T::DbWeight::get().reads(28 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } +} + // For backwards compatibility and tests impl WeightInfo for () { + // Storage: NominationPools MinJoinBond (r:1 w:0) + // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:0) + // Storage: System Account (r:2 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:3 w:3) + // Storage: BagsList ListBags (r:2 w:2) + // Storage: NominationPools CounterForDelegators (r:1 w:1) fn join() -> Weight { - 0 + (109_762_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(15 as Weight)) + .saturating_add(RocksDbWeight::get().writes(11 as Weight)) } + // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - 0 + (45_848_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } + // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:2 w:1) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:3 w:3) + // Storage: Staking Bonded (r:1 w:0) + // Storage: BagsList ListBags (r:2 w:2) + // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond_other() -> Weight { - 0 + (114_657_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(18 as Weight)) + .saturating_add(RocksDbWeight::get().writes(13 as Weight)) } - fn pool_withdraw_unbonded(_s: u32) -> Weight { - 0 + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + fn pool_withdraw_unbonded(s: u32, ) -> Weight { + (33_087_000 as Weight) + // Standard Error: 0 + .saturating_add((50_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } - fn withdraw_unbonded_other(_s: u32) -> Weight { - 0 + // Storage: NominationPools Delegators (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: NominationPools CounterForDelegators (r:1 w:1) + fn withdraw_unbonded_other(s: u32, ) -> Weight { + (66_522_000 as Weight) + // Standard Error: 0 + .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(RocksDbWeight::get().reads(8 as Weight)) + .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: NominationPools MinCreateBond (r:1 w:0) + // Storage: NominationPools MaxPools (r:1 w:0) + // Storage: NominationPools CounterForBondedPools (r:1 w:1) + // Storage: NominationPools Delegators (r:1 w:1) + // Storage: System ParentHash (r:1 w:0) + // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking HistoryDepth (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: NominationPools CounterForDelegators (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - 0 + (92_839_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(17 as Weight)) + .saturating_add(RocksDbWeight::get().writes(11 as Weight)) } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking MaxNominatorsCount (r:1 w:0) + // Storage: Staking Validators (r:17 w:0) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: BagsList ListNodes (r:1 w:1) + // Storage: BagsList ListBags (r:1 w:1) + // Storage: BagsList CounterForListNodes (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) fn nominate() -> Weight { - 0 + (79_587_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(28 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } } From 5b33fbba9fb916d390a033ed61520e6c44686b36 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Feb 2022 19:11:39 -0800 Subject: [PATCH 127/299] Benchmark withdraw_unbonded_other_kill --- .../nomination-pools/benchmarking/src/lib.rs | 77 +++++++++++++++++-- frame/nomination-pools/src/lib.rs | 44 ++++++----- 2 files changed, 96 insertions(+), 25 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index ec00c4750f2c1..bab48de9dbc44 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -336,14 +336,13 @@ frame_benchmarking::benchmarks! { } // TODO: setup a withdraw unbonded kill scenario, make variable over slashing spans - withdraw_unbonded_other { + withdraw_unbonded_other_update { let s in 0 .. MAX_SPANS; clear_storage::(); - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Add a new delegator @@ -375,7 +374,7 @@ frame_benchmarking::benchmarks! { pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); whitelist_account!(joiner); - }: _(Origin::Signed(joiner.clone()), joiner.clone(), s) + }: withdraw_unbonded_other(Origin::Signed(joiner.clone()), joiner.clone(), s) verify { assert_eq!( CurrencyOf::::free_balance(&joiner), @@ -385,6 +384,74 @@ frame_benchmarking::benchmarks! { assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 0); } + withdraw_unbonded_other_kill { + let s in 0 .. MAX_SPANS; + clear_storage::(); + + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + + // We set the pool to the destroying state so the depositor can leave + BondedPools::::try_mutate(&pool_account, |maybe_bonded_pool| { + maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { + bonded_pool.state = PoolState::Destroying; + }) + }) + .unwrap(); + + // Unbond the creator + pallet_staking::CurrentEra::::put(0); + Pools::::unbond_other(Origin::Signed(depositor.clone()).into(), depositor.clone()).unwrap(); + + // Sanity check that unbond worked + assert_eq!( + T::StakingInterface::bonded_balance(&pool_account).unwrap(), + Zero::zero() + ); + assert_eq!( + CurrencyOf::::free_balance(&pool_account), + min_create_bond + ); + assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); + + // Set the current era to ensure we can withdraw unbonded funds + pallet_staking::CurrentEra::::put(EraIndex::max_value()); + + // Some last checks that storage items we expect to get cleaned up are present + assert!(pallet_staking::Ledger::::contains_key(&pool_account)); + assert!(BondedPools::::contains_key(&pool_account)); + assert!(SubPoolsStorage::::contains_key(&pool_account)); + assert!(RewardPools::::contains_key(&pool_account)); + assert!(Delegators::::contains_key(&depositor)); + let reward_account = pallet_nomination_pools::RewardPools::::get(&pool_account) + .map(|r| r.account.clone()) + .expect("pool created above."); + // Simulate some rewards so we can check if the rewards storage is cleaned up + CurrencyOf::::make_free_balance_be(&reward_account, CurrencyOf::::minimum_balance()); + assert!(frame_system::Account::::contains_key(&reward_account)); + + whitelist_account!(depositor); + }: withdraw_unbonded_other(Origin::Signed(depositor.clone()), depositor.clone(), s) + verify { + // Pool removal worked + assert!(!pallet_staking::Ledger::::contains_key(&pool_account)); + assert!(!BondedPools::::contains_key(&pool_account)); + assert!(!SubPoolsStorage::::contains_key(&pool_account)); + assert!(!RewardPools::::contains_key(&pool_account)); + assert!(!Delegators::::contains_key(&depositor)); + assert!(!frame_system::Account::::contains_key(&pool_account)); + assert!(!frame_system::Account::::contains_key(&reward_account)); + + // Funds where transferred back correctly + assert_eq!( + CurrencyOf::::free_balance(&depositor), + min_create_bond * 2u32.into() + ); + } + create { clear_storage::(); diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 63ffb94176266..54de6bf3c31e1 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -367,8 +367,9 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), + (true, true) | (false, true) => { + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) + }, (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -393,7 +394,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero() + return Zero::zero(); } // Equivalent of (current_balance / current_points) * delegator_points @@ -545,8 +546,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) < - BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) + < BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -722,7 +723,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self + return self; } // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -762,7 +763,7 @@ impl SubPools { } /// The maximum amount of eras an unbonding pool can exist prior to being merged with the -/// `no_era pool. This is guaranteed to at least be equal to the staking `UnbondingDuration`. For +/// `no_era` pool. This is guaranteed to at least be equal to the staking `UnbondingDuration`. For /// improved UX [`Config::PostUnbondingPoolsWindow`] should be configured to a non-zero value. pub struct TotalUnbondingPools(PhantomData); impl Get for TotalUnbondingPools { @@ -1089,7 +1090,7 @@ pub mod pallet { /// Call `withdraw_unbonded` for the pools account. This call can be made by any account. /// - /// This is useful if their are too many unlocking chunks to unbond, and some can be cleared + /// This is useful if their are too many unlocking chunks to call `unbond_other`, and some can be cleared /// by withdrawing. #[pallet::weight(666)] pub fn pool_withdraw_unbonded( @@ -1098,6 +1099,10 @@ pub mod pallet { num_slashing_spans: u32, ) -> DispatchResult { let _ = ensure_signed(origin)?; + let pool = BondedPool::::get(&pool_account).ok_or(Error::::PoolNotFound)?; + // For now we only allow a pool to withdraw unbonded if its not destroying. If the pool + // is destroying then withdraw_unbonded_other can be used. + ensure!(pool.state != PoolState::Destroying, Error::::NotDestroying); T::StakingInterface::withdraw_unbonded(pool_account, num_slashing_spans)?; Ok(()) } @@ -1127,8 +1132,8 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); ensure!( - current_era.saturating_sub(unbonding_era) >= - T::StakingInterface::bonding_duration(), + current_era.saturating_sub(unbonding_era) + >= T::StakingInterface::bonding_duration(), Error::::NotUnbondedYet ); @@ -1161,6 +1166,8 @@ pub mod pallet { }; T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; + // TODO Make sure T::Currency::free_balance(&delegator.pool) doesn't include bonded + // else this check is bad if T::Currency::free_balance(&delegator.pool) >= balance_to_unbond { T::Currency::transfer( &delegator.pool, @@ -1194,6 +1201,7 @@ pub mod pallet { T::Currency::make_free_balance_be(&reward_pool.account, Zero::zero()); T::Currency::make_free_balance_be(&bonded_pool.account, Zero::zero()); bonded_pool.remove(); + // Event destroyed } else { SubPoolsStorage::::insert(&delegator.pool, sub_pools); } @@ -1202,10 +1210,6 @@ pub mod pallet { Ok(()) } - // TODO: [now] in addition to a min bond, we could take a fee/deposit. This discourage - // people from creating a pool when they just could be a normal nominator. - /// Create a pool. - /// /// Note that the pool creator will delegate `amount` to the pool and cannot unbond until /// every /// NOTE: This does not nominate, a pool admin needs to call [`Call::nominate`] @@ -1225,8 +1229,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() && - amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() + && amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1381,9 +1385,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() || - current_points.is_zero() || - reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() + || current_points.is_zero() + || reward_pool.balance.is_zero() { Zero::zero() } else { @@ -1475,7 +1479,7 @@ impl Pallet { return Some(SlashPoolOut { slashed_bonded: Zero::zero(), slashed_unlocking: Default::default(), - }) + }); } let slashed_unlocking: BTreeMap<_, _> = affected_range .filter_map(|era| { From a8828de637d0650c299251ccf9e0ce0fda34d8a8 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Feb 2022 19:16:02 -0800 Subject: [PATCH 128/299] Delete old benchmarking files --- frame/nomination-pools/src/benchmarking.rs | 421 --------------------- 1 file changed, 421 deletions(-) delete mode 100644 frame/nomination-pools/src/benchmarking.rs diff --git a/frame/nomination-pools/src/benchmarking.rs b/frame/nomination-pools/src/benchmarking.rs deleted file mode 100644 index 1dab8c68306a4..0000000000000 --- a/frame/nomination-pools/src/benchmarking.rs +++ /dev/null @@ -1,421 +0,0 @@ -// use super::*; -// use crate::Pallet as Pools; -// use frame_benchmarking::{account, vec, whitelist_account}; -// use frame_system::RawOrigin as Origin; - -// const USER_SEED: u32 = 0; -// const MAX_SPANS: u32 = 100; - -// fn clear_storage() { -// BondedPools::::remove_all(); -// RewardPools::::remove_all(); -// SubPoolsStorage::::remove_all(); -// Delegators::::remove_all(); -// } - -// fn create_funded_user_with_balance( -// string: &'static str, -// n: u32, -// balance: BalanceOf, -// ) -> T::AccountId { -// let user = account(string, n, USER_SEED); -// T::Currency::make_free_balance_be(&user, balance); -// user -// } - -// // Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free -// // balance. -// fn create_pool_account(n: u32, balance: BalanceOf) -> (T::AccountId, T::AccountId) -// { let pool_creator: T::AccountId = -// create_funded_user_with_balance::("pool_creator", n, balance * 2u32.into()); - -// Pools::::create( -// Origin::Signed(pool_creator.clone()).into(), -// balance, -// n as u16, -// pool_creator.clone(), -// pool_creator.clone(), -// pool_creator.clone(), -// ) -// .unwrap(); - -// let (pool_account, _) = BondedPools::::iter() -// .find(|(_, bonded_pool)| bonded_pool.depositor == pool_creator) -// .expect("pool_creator created a pool above"); - -// (pool_creator, pool_account) -// } - -// struct ListScenario { -// /// Stash/Controller that is expected to be moved. -// origin1: T::AccountId, -// dest_weight: BalanceOf, -// origin1_delegator: Option, -// } - -// impl ListScenario { -// /// An expensive scenario for bags-list implementation: -// /// -// /// - the node to be updated (r) is the head of a bag that has at least one other node. The bag -// /// itself will need to be read and written to update its head. The node pointed to by r.next -// /// will need to be read and written as it will need to have its prev pointer updated. Note -// /// that there are two other worst case scenarios for bag removal: 1) the node is a tail and -// /// 2) the node is a middle node with prev and next; all scenarios end up with the same number -// /// of storage reads and writes. -// /// -// /// - the destination bag has at least one node, which will need its next pointer updated. -// /// -// /// NOTE: while this scenario specifically targets a worst case for the bags-list, it should -// /// also elicit a worst case for other known `SortedListProvider` implementations; although -// /// this may not be true against unknown `SortedListProvider` implementations. -// pub(crate) fn new( -// origin_weight: BalanceOf, -// is_increase: bool, -// ) -> Result { -// ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); - -// if MaxPools::::get().unwrap_or(0) < 3 { -// // TODO: this is due to mock config, probably best to change -// MaxPools::::put(3); -// } - -// // Burn the entire issuance. -// // TODO: probably don't need this -// let i = T::Currency::burn(T::Currency::total_issuance()); -// sp_std::mem::forget(i); - -// // create accounts with the origin weight -// let (_, pool_origin1) = create_pool_account::(USER_SEED + 2, origin_weight); -// T::StakingInterface::nominate( -// pool_origin1.clone(), -// // NOTE: these don't really need to be validators. -// vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], -// )?; - -// let (_, pool_origin2) = create_pool_account::(USER_SEED + 3, origin_weight); -// T::StakingInterface::nominate( -// pool_origin2, -// vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))].clone(), -// )?; - -// // find a destination weight that will trigger the worst case scenario -// let dest_weight_as_vote = -// T::SortedListProvider::weight_update_worst_case(&pool_origin1, is_increase); - -// let dest_weight: BalanceOf = -// dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; - -// // create an account with the worst case destination weight -// let (_, pool_dest1) = create_pool_account::(USER_SEED + 1, dest_weight); -// T::StakingInterface::nominate( -// pool_dest1, -// vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], -// )?; - -// Ok(ListScenario { origin1: pool_origin1, dest_weight, origin1_delegator: None }) -// } - -// fn add_joiner(mut self, amount: BalanceOf) -> Self { -// let amount = MinJoinBond::::get() -// .max(T::Currency::minimum_balance()) -// // max the `given` amount with minimum thresholds for account balance and joining a pool -// // to ensure 1. the user can be created and 2. can join the pool -// .max(amount); - -// let joiner: T::AccountId = account("joiner", USER_SEED, 0); -// self.origin1_delegator = Some(joiner.clone()); -// T::Currency::make_free_balance_be(&joiner, amount * 2u32.into()); - -// let current_bonded = T::StakingInterface::bonded_balance(&self.origin1).unwrap(); - -// // Unbond `amount` from the underlying pool account so when the delegator joins -// // we will maintain `current_bonded` -// T::StakingInterface::unbond(self.origin1.clone(), amount).unwrap(); - -// // Account pool points for the unbonded balance -// BondedPools::::mutate(&self.origin1, |maybe_pool| { -// maybe_pool.as_mut().map(|pool| pool.points -= amount) -// }); - -// Pools::::join(Origin::Signed(joiner.clone()).into(), amount, self.origin1.clone()) -// .unwrap(); - -// assert_eq!(T::StakingInterface::bonded_balance(&self.origin1).unwrap(), current_bonded); - -// self -// } -// } - -// frame_benchmarking::benchmarks! { -// join { -// clear_storage::(); - -// let origin_weight = MinCreateBond::::get().max(T::Currency::minimum_balance()) * 2u32.into(); - -// // setup the worst case list scenario. -// let scenario = ListScenario::::new(origin_weight, true)?; -// assert_eq!( -// T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), -// origin_weight -// ); - -// let max_additional = scenario.dest_weight.clone() - origin_weight; -// let joiner_free = T::Currency::minimum_balance() + max_additional; - -// let joiner: T::AccountId -// = create_funded_user_with_balance::("joiner", 0, joiner_free); - -// whitelist_account!(joiner); -// }: _(Origin::Signed(joiner.clone()), max_additional, scenario.origin1.clone()) -// verify { -// assert_eq!(T::Currency::free_balance(&joiner), joiner_free - max_additional); -// assert_eq!( -// T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), -// scenario.dest_weight -// ); -// } - -// claim_payout { -// clear_storage::(); - -// let origin_weight = MinCreateBond::::get().max(T::Currency::minimum_balance()) * 2u32.into(); -// let (depositor, pool_account) = create_pool_account::(0, origin_weight); - -// let reward_account = RewardPools::::get( -// pool_account -// ) -// .unwrap() -// .account; - -// // Send funds to the reward account of the pool -// T::Currency::make_free_balance_be(&reward_account, origin_weight); - -// // Sanity check -// assert_eq!( -// T::Currency::free_balance(&depositor), -// origin_weight -// ); - -// whitelist_account!(depositor); -// }:_(Origin::Signed(depositor.clone())) -// verify { -// assert_eq!( -// T::Currency::free_balance(&depositor), -// origin_weight * 2u32.into() -// ); -// assert_eq!( -// T::Currency::free_balance(&reward_account), -// Zero::zero() -// ); -// } - -// unbond_other { -// clear_storage::(); - -// // the weight the nominator will start at. The value used here is expected to be -// // significantly higher than the first position in a list (e.g. the first bag threshold). -// let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) -// .map_err(|_| "balance expected to be a u128") -// .unwrap(); -// let scenario = ListScenario::::new(origin_weight, false)?; - -// let amount = origin_weight - scenario.dest_weight.clone(); - -// let scenario = scenario.add_joiner(amount); - -// let delegator = scenario.origin1_delegator.unwrap().clone(); -// }: _(Origin::Signed(delegator.clone()), delegator.clone()) -// verify { -// assert!( -// T::StakingInterface::bonded_balance(&scenario.origin1).unwrap() -// <= scenario.dest_weight.clone() -// ); -// } - -// // TODO: setup a withdraw unbonded kill scenario -// pool_withdraw_unbonded { -// let s in 0 .. MAX_SPANS; -// clear_storage::(); - -// let min_create_bond = MinCreateBond::::get() -// .max(T::StakingInterface::minimum_bond()) -// .max(T::Currency::minimum_balance()); -// let (depositor, pool_account) = create_pool_account::(0, min_create_bond); - -// // Add a new delegator -// let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); -// let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); -// Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) -// .unwrap(); - -// // Sanity check join worked -// assert_eq!( -// T::StakingInterface::bonded_balance(&pool_account).unwrap( - -// ), -// min_create_bond + min_join_bond -// ); -// assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); - -// // Unbond the new delegator -// Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); - -// // Sanity check that unbond worked -// assert_eq!( -// T::StakingInterface::bonded_balance(&pool_account).unwrap(), -// min_create_bond -// ); -// // Set the current era -// T::StakingInterface::set_current_era(EraIndex::max_value()); - -// // T::StakingInterface::add_slashing_spans(&pool_account, s); -// whitelist_account!(pool_account); -// }: _(Origin::Signed(pool_account.clone()), pool_account.clone(), 2) -// verify { -// // The joiners funds didn't change -// assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); - -// // TODO: figure out if we can check anything else. Its tricky because the free balance hasn't -// // changed and I don't we don't have an api from here to the unlocking chunks, or staking balance -// lock } - -// // TODO: setup a withdraw unbonded kill scenario, make variable over slashing spans -// withdraw_unbonded_other { -// let s in 0 .. MAX_SPANS; -// clear_storage::(); - -// // T::StakingInterface::add_slashing_spans(&stash, s); - -// let min_create_bond = MinCreateBond::::get() -// .max(T::StakingInterface::minimum_bond()) -// .max(T::Currency::minimum_balance()); -// let (depositor, pool_account) = create_pool_account::(0, min_create_bond); - -// // Add a new delegator -// let min_join_bond = MinJoinBond::::get().max(T::Currency::minimum_balance()); -// let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); -// Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) -// .unwrap(); - -// // Sanity check join worked -// assert_eq!( -// T::StakingInterface::bonded_balance(&pool_account).unwrap( - -// ), -// min_create_bond + min_join_bond -// ); -// assert_eq!(T::Currency::free_balance(&joiner), min_join_bond); - -// // Unbond the new delegator -// T::StakingInterface::set_current_era(0); -// Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); - -// // Sanity check that unbond worked -// assert_eq!( -// T::StakingInterface::bonded_balance(&pool_account).unwrap(), -// min_create_bond -// ); - -// // Set the current era to ensure we can withdraw unbonded funds -// T::StakingInterface::set_current_era(EraIndex::max_value()); - -// // T::StakingInterface::add_slashing_spans(&pool_account, s); -// whitelist_account!(joiner); -// }: _(Origin::Signed(joiner.clone()), joiner.clone(), 0) -// verify { -// assert_eq!( -// T::Currency::free_balance(&joiner), -// min_join_bond * 2u32.into() -// ); -// } - -// create { -// clear_storage::(); - -// let min_create_bond = MinCreateBond::::get() -// .max(T::StakingInterface::minimum_bond()) -// .max(T::Currency::minimum_balance()); -// let depositor: T::AccountId = account("depositor", USER_SEED, 0); - -// // Give the depositor some balance to bond -// T::Currency::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); - -// // Make sure no pools exist as a pre-condition for our verify checks -// assert_eq!(RewardPools::::count(), 0); -// assert_eq!(BondedPools::::count(), 0); - -// whitelist_account!(depositor); -// }: _( -// Origin::Signed(depositor.clone()), -// min_create_bond, -// 0, -// depositor.clone(), -// depositor.clone(), -// depositor.clone() -// ) -// verify { -// assert_eq!(RewardPools::::count(), 1); -// assert_eq!(BondedPools::::count(), 1); -// let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); -// assert_eq!( -// new_pool, -// BondedPoolStorage { -// points: min_create_bond, -// depositor: depositor.clone(), -// root: depositor.clone(), -// nominator: depositor.clone(), -// state_toggler: depositor.clone(), -// state: PoolState::Open, -// } -// ); -// assert_eq!( -// T::StakingInterface::bonded_balance(&pool_account), -// Some(min_create_bond) -// ); -// } - -// nominate { -// clear_storage::(); - -// // Create a pool -// let min_create_bond = MinCreateBond::::get() -// .max(T::StakingInterface::minimum_bond()) -// .max(T::Currency::minimum_balance()); -// 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 actual -// validators let validators: Vec<_> = (0..T::StakingInterface::max_nominations()) -// .map(|i| -// T::Lookup::unlookup(account("stash", USER_SEED, i)) -// ) -// .collect(); - -// whitelist_account!(depositor); -// }:_(Origin::Signed(depositor.clone()), pool_account, validators) -// verify { -// assert_eq!(RewardPools::::count(), 1); -// assert_eq!(BondedPools::::count(), 1); -// let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); -// assert_eq!( -// new_pool, -// BondedPoolStorage { -// points: min_create_bond, -// depositor: depositor.clone(), -// root: depositor.clone(), -// nominator: depositor.clone(), -// state_toggler: depositor.clone(), -// state: PoolState::Open, -// } -// ); -// assert_eq!( -// T::StakingInterface::bonded_balance(&pool_account), -// Some(min_create_bond) -// ); -// } -// } - -// frame_benchmarking::impl_benchmark_test_suite!( -// Pallet, -// crate::mock::ExtBuilder::default().build(), -// crate::mock::Runtime -// ); From d8cdbf20cda94109a1ec32dce16ff3e9291c53ec Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Feb 2022 20:03:03 -0800 Subject: [PATCH 129/299] Refunds for withdraw_unbonded --- frame/nomination-pools/src/lib.rs | 54 +++++++++++++-------------- frame/nomination-pools/src/weights.rs | 22 +++++++++-- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 54de6bf3c31e1..7cc2b61c21a02 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -339,9 +339,6 @@ use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZer use sp_staking::{EraIndex, PoolsInterface, SlashPoolArgs, SlashPoolOut, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div, vec::Vec}; -#[cfg(feature = "runtime-benchmarks")] -pub mod benchmarking; - #[cfg(test)] mod mock; #[cfg(test)] @@ -367,9 +364,8 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => { - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) - }, + (true, true) | (false, true) => + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -394,7 +390,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero(); + return Zero::zero() } // Equivalent of (current_balance / current_points) * delegator_points @@ -546,8 +542,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) - < BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) < + BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -560,7 +556,7 @@ impl BondedPool { } fn can_kick(&self, who: &T::AccountId) -> bool { - (*who == self.root || *who == self.state_toggler) && self.state == PoolState::Blocked + *who == self.root || *who == self.state_toggler && self.state == PoolState::Blocked } fn is_destroying(&self) -> bool { @@ -723,7 +719,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self; + return self } // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -1090,8 +1086,8 @@ pub mod pallet { /// Call `withdraw_unbonded` for the pools account. This call can be made by any account. /// - /// This is useful if their are too many unlocking chunks to call `unbond_other`, and some can be cleared - /// by withdrawing. + /// This is useful if their are too many unlocking chunks to call `unbond_other`, and some + /// can be cleared by withdrawing. #[pallet::weight(666)] pub fn pool_withdraw_unbonded( origin: OriginFor, @@ -1121,19 +1117,21 @@ pub mod pallet { /// - The caller is the target and they are not the depositor. /// /// Note: If the target is the depositor, the pool will be destroyed. - #[pallet::weight(666)] + #[pallet::weight( + T::WeightInfo::withdraw_unbonded_other_kill(*num_slashing_spans) + )] pub fn withdraw_unbonded_other( origin: OriginFor, target: T::AccountId, num_slashing_spans: u32, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { let caller = ensure_signed(origin)?; let delegator = Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); ensure!( - current_era.saturating_sub(unbonding_era) - >= T::StakingInterface::bonding_duration(), + current_era.saturating_sub(unbonding_era) >= + T::StakingInterface::bonding_duration(), Error::::NotUnbondedYet ); @@ -1191,7 +1189,7 @@ pub mod pallet { }); } - if should_remove_pool { + let post_info_weight = if should_remove_pool { let reward_pool = RewardPools::::take(&delegator.pool) .defensive_ok_or_else(|| Error::::PoolNotFound)?; SubPoolsStorage::::remove(&delegator.pool); @@ -1201,13 +1199,15 @@ pub mod pallet { T::Currency::make_free_balance_be(&reward_pool.account, Zero::zero()); T::Currency::make_free_balance_be(&bonded_pool.account, Zero::zero()); bonded_pool.remove(); - // Event destroyed + // TODO: Event destroyed + None } else { SubPoolsStorage::::insert(&delegator.pool, sub_pools); - } + Some(T::WeightInfo::withdraw_unbonded_other_update(num_slashing_spans)) + }; Delegators::::remove(&target); - Ok(()) + Ok(post_info_weight.into()) } /// Note that the pool creator will delegate `amount` to the pool and cannot unbond until @@ -1229,8 +1229,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() - && amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() && + amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1385,9 +1385,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() - || current_points.is_zero() - || reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() || + current_points.is_zero() || + reward_pool.balance.is_zero() { Zero::zero() } else { @@ -1479,7 +1479,7 @@ impl Pallet { return Some(SlashPoolOut { slashed_bonded: Zero::zero(), slashed_unlocking: Default::default(), - }); + }) } let slashed_unlocking: BTreeMap<_, _> = affected_range .filter_map(|era| { diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index 0054490532c33..fd785306ca8ce 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -48,7 +48,8 @@ pub trait WeightInfo { fn claim_payout() -> Weight; fn unbond_other() -> Weight; fn pool_withdraw_unbonded(s: u32, ) -> Weight; - fn withdraw_unbonded_other(s: u32, ) -> Weight; + fn withdraw_unbonded_other_update(s: u32, ) -> Weight; + fn withdraw_unbonded_other_kill(s: u32, ) -> Weight; fn create() -> Weight; fn nominate() -> Weight; } @@ -118,7 +119,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForDelegators (r:1 w:1) - fn withdraw_unbonded_other(s: u32, ) -> Weight { + fn withdraw_unbonded_other_update(s: u32, ) -> Weight { + (66_522_000 as Weight) + // Standard Error: 0 + .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(T::DbWeight::get().reads(8 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) + } + fn withdraw_unbonded_other_kill(s: u32, ) -> Weight { (66_522_000 as Weight) // Standard Error: 0 .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) @@ -231,7 +239,15 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForDelegators (r:1 w:1) - fn withdraw_unbonded_other(s: u32, ) -> Weight { + fn withdraw_unbonded_other_update(s: u32) -> Weight { + (66_522_000 as Weight) + // Standard Error: 0 + .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(RocksDbWeight::get().reads(8 as Weight)) + .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + } + // Storage: NominationPools CounterForDelegators (r:1 w:1) + fn withdraw_unbonded_other_kill(s: u32) -> Weight { (66_522_000 as Weight) // Standard Error: 0 .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) From 6aa5463990be27a1972273a972ace1ebdf376a70 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Feb 2022 20:43:45 -0800 Subject: [PATCH 130/299] Remove some TODOs --- frame/nomination-pools/benchmarking/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index bab48de9dbc44..7a8c3add9f57f 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -17,8 +17,7 @@ use pallet_nomination_pools::{ }; use sp_runtime::traits::{StaticLookup, Zero}; use sp_staking::{EraIndex, StakingInterface}; - -// `frame_benchmarking::benchmarks!` macro code needs this +// `frame_benchmarking::benchmarks!` macro needs this use pallet_nomination_pools::Call; type CurrencyOf = ::Currency; @@ -40,7 +39,6 @@ fn clear_storage() { pallet_nomination_pools::Delegators::::remove_all(); } -// TODO: [now] replace this with staking test helper util fn create_funded_user_with_balance( string: &'static str, n: u32, @@ -289,7 +287,6 @@ frame_benchmarking::benchmarks! { assert_eq!(delegator.unbonding_era, Some(0)); } - // TODO: setup a withdraw unbonded kill scenario pool_withdraw_unbonded { let s in 0 .. MAX_SPANS; clear_storage::(); @@ -335,7 +332,6 @@ frame_benchmarking::benchmarks! { assert_eq!(pallet_staking::Ledger::::get(pool_account).unwrap().unlocking.len(), 0); } - // TODO: setup a withdraw unbonded kill scenario, make variable over slashing spans withdraw_unbonded_other_update { let s in 0 .. MAX_SPANS; clear_storage::(); From ad033c0ef6c4d5236ebbdf63b5d411ab7044465f Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 23 Feb 2022 20:11:52 -0800 Subject: [PATCH 131/299] 'Don't return an option for the current_era' --- frame/nomination-pools/benchmarking/src/lib.rs | 3 +-- frame/nomination-pools/src/lib.rs | 6 ++---- frame/nomination-pools/src/mock.rs | 4 ++-- frame/staking/src/pallet/impls.rs | 4 ++-- primitives/staking/src/lib.rs | 4 +--- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 7a8c3add9f57f..66098668b4692 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -12,8 +12,7 @@ use frame_support::{ensure, traits::Get}; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ BalanceOf, BondedPoolStorage, BondedPools, Delegators, MinCreateBond, MinJoinBond, - Pallet as Pools, PoolState, RewardPools, SubPools, SubPoolsStorage, SubPoolsWithEra, - UnbondPool, + Pallet as Pools, PoolState, RewardPools, SubPoolsStorage, UnbondPool, }; use sp_runtime::traits::{StaticLookup, Zero}; use sp_staking::{EraIndex, StakingInterface}; diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 7cc2b61c21a02..0c15f40dbcea3 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -319,7 +319,6 @@ // pools is passed the updated unlocking chunks and makes updates based on that // - benchmarks // - make staking interface current era -// - staking provider current era should not return option, just era index // - write detailed docs for StakingInterface // Ensure we're `no_std` when compiling for Wasm. @@ -1049,7 +1048,7 @@ pub mod pallet { Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPoolsStorage::::get(&delegator.pool).unwrap_or_default(); - let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); + let current_era = T::StakingInterface::current_era(); let balance_to_unbond = bonded_pool.balance_to_unbond(delegator.points); @@ -1128,7 +1127,7 @@ pub mod pallet { let caller = ensure_signed(origin)?; let delegator = Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; - let current_era = T::StakingInterface::current_era().unwrap_or(Zero::zero()); + let current_era = T::StakingInterface::current_era(); ensure!( current_era.saturating_sub(unbonding_era) >= T::StakingInterface::bonding_duration(), @@ -1199,7 +1198,6 @@ pub mod pallet { T::Currency::make_free_balance_be(&reward_pool.account, Zero::zero()); T::Currency::make_free_balance_be(&bonded_pool.account, Zero::zero()); bonded_pool.remove(); - // TODO: Event destroyed None } else { SubPoolsStorage::::insert(&delegator.pool, sub_pools); diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 9863ec9292c96..73b2674d60263 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -35,8 +35,8 @@ impl sp_staking::StakingInterface for StakingMock { 10 } - fn current_era() -> Option { - Some(CurrentEra::get()) + fn current_era() -> EraIndex { + CurrentEra::get() } fn bonding_duration() -> EraIndex { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index a44507fa4fb7a..fb466f1d91c0b 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1328,8 +1328,8 @@ impl StakingInterface for Pallet { T::BondingDuration::get() } - fn current_era() -> Option { - Self::current_era() + fn current_era() -> EraIndex { + Self::current_era().unwrap_or(Zero::zero()) } fn bonded_balance(controller: &Self::AccountId) -> Option { diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index c75e1a7082904..56ba608a70eb5 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -98,9 +98,7 @@ pub trait StakingInterface { fn bonding_duration() -> EraIndex; /// The current era for the staking system. - // TODO: Is it ok to assume None is always era zero? If so, then in the impl we ca do - // unwrap_or(0) - fn current_era() -> Option; + fn current_era() -> EraIndex; /// Balance `controller` has bonded for nominating. fn bonded_balance(controller: &Self::AccountId) -> Option; From 61a1da19d9f38cb614a64f8108dd87c92f9fe5d8 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 23 Feb 2022 20:53:08 -0800 Subject: [PATCH 132/299] Streamline extrinsic docs --- frame/nomination-pools/src/lib.rs | 50 +++++++++++++------------------ 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 0c15f40dbcea3..1c4742900ca53 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -23,20 +23,15 @@ //! //! ### Joining //! -//! A account can stake funds with a nomination pool by calling [`Call::join`]. The amount to bond -//! is transferred from the delegator to the pools account and immediately increases the pools bond. -//! Note that an account can only belong to one pool at a time. +//! A account can stake funds with a nomination pool by calling [`Call::join`]. //! -//! For more detailed docs see the [joining](#joining) section. +//! For more design docs see the [joining](#joining) section. //! //! ### Claiming rewards //! -//! The delegator will earn rewards pro rata based on the delegators stake vs the sum of the -//! delegators in the pools stake. In order to claim their share of rewards a delegator must call -//! [`Call::claim_payout`]. A delegator can start claiming rewards in the era after they join; -//! rewards can be claimed periodically at any time after that and do not "expire". +//! After joining a pool, a delegator can claim rewards by calling [`Call::claim_payout`]. //! -//! For more detailed docs see the [reward pool](#reward-pool) section. +//! For more design docs see the [reward pool](#reward-pool) section. //! //! ### Leaving //! @@ -86,13 +81,6 @@ //! Note: if it is desired that any of the admin roles are not accessible, they can be set to an //! anonymous proxy account that has no proxies (and is thus provably keyless). //! -//! **Relevant extrinsics:** -//! -//! * [`Call::create`] -//! * [`Call::nominate`] -//! * [`Call::unbond_other`] -//! * [`Call::withdraw_unbonded_other`] -//! //! ## Design //! //! _Notes_: this section uses pseudo code to explain general design and does not necessarily @@ -315,11 +303,8 @@ // TODO // - Refactor staking slashing to always slash unlocking chunks (then back port) -// - backport making ledger generic over ^^ IDEA: maybe staking can slash unlocking chunks, and then -// pools is passed the updated unlocking chunks and makes updates based on that -// - benchmarks -// - make staking interface current era // - write detailed docs for StakingInterface +// - various backports // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -928,7 +913,8 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Join a pre-existing pool. + /// Stake funds with a pool. The amount to bond is transferred from the delegator to the + /// pools account and immediately increases the pools bond. /// /// Notes /// * an account can only be a member of a single pool. @@ -996,9 +982,10 @@ pub mod pallet { /// A bonded delegator can use this to claim their payout based on the rewards that the pool /// has accumulated since their last claimed payout (OR since joining if this is there first - /// time claiming rewards). + /// time claiming rewards). The payout will be transffered to the delegator's account. /// - /// Note that the payout will go to the delegator's account. + /// The delegator will earn rewards pro rata based on the delegators stake vs the sum of the + /// delegators in the pools stake. Rewards do not "expire". #[pallet::weight(T::WeightInfo::claim_payout())] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -1096,7 +1083,7 @@ pub mod pallet { let _ = ensure_signed(origin)?; let pool = BondedPool::::get(&pool_account).ok_or(Error::::PoolNotFound)?; // For now we only allow a pool to withdraw unbonded if its not destroying. If the pool - // is destroying then withdraw_unbonded_other can be used. + // is destroying then `withdraw_unbonded_other` can be used. ensure!(pool.state != PoolState::Destroying, Error::::NotDestroying); T::StakingInterface::withdraw_unbonded(pool_account, num_slashing_spans)?; Ok(()) @@ -1208,13 +1195,18 @@ pub mod pallet { Ok(post_info_weight.into()) } - /// Note that the pool creator will delegate `amount` to the pool and cannot unbond until - /// every - /// NOTE: This does not nominate, a pool admin needs to call [`Call::nominate`] + /// Create a new delegation pool. + /// + /// # Parameters /// - /// * `amount`: Balance to delegate to the pool. Must meet the minimum bond. - /// * `index`: Disambiguation index for seeding account generation. Likely only useful when + /// * `amount`: The amount of funds to delegate to the pool. This also acts of a sort of + /// deposit since the pools creator cannot fully unbond funds until the pool is being + /// destroyed. + /// * `index`: A disambiguation index for creating the account. Likely only useful when /// creating multiple pools in the same extrinsic. + /// * `root`: The account to set as [`BondedPool::root`]. + /// * `nominator`: The account to set as the [`BondedPool::nominator`]. + /// * `state_toggler`: The account to set as the [`BondedPool::state_toggler`]. #[pallet::weight(666)] #[frame_support::transactional] pub fn create( From 0cb35f6f220b28dafd8bea3d9c2bed35092dbca2 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 23 Feb 2022 21:11:30 -0800 Subject: [PATCH 133/299] small docs tweaks --- frame/nomination-pools/src/lib.rs | 19 +++++++++---------- primitives/staking/src/lib.rs | 7 +++++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 1c4742900ca53..ce5ebdda56cd3 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -21,19 +21,19 @@ //! //! ## Usage //! -//! ### Joining +//! ### Join //! //! A account can stake funds with a nomination pool by calling [`Call::join`]. //! -//! For more design docs see the [joining](#joining) section. +//! For design docs see the [joining](#joining) section. //! -//! ### Claiming rewards +//! ### Claim rewards //! //! After joining a pool, a delegator can claim rewards by calling [`Call::claim_payout`]. //! -//! For more design docs see the [reward pool](#reward-pool) section. +//! For design docs see the [reward pool](#reward-pool) section. //! -//! ### Leaving +//! ### Leave //! //! In order to leave, a delegator must take two steps. //! @@ -43,7 +43,7 @@ //! Second, Once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the delegator //! can call [`Call::withdraw_unbonded_other`] to withdraw all there funds. //! -//! For more detailed docs see the [bonded pool](#bonded-pool) and [unbonding sub +//! For design docs see the [bonded pool](#bonded-pool) and [unbonding sub //! pools](#unbonding-sub-pools) sections. //! //! ### Slashes @@ -53,7 +53,7 @@ //! bonded in the aforementioned range of eras will be affected by the slash. A delegator is slashed //! pro-rata based on its stake relative to the total slash amount. //! -//! For more detailed docs see the [slashing](#slashing) section. +//! For design docs see the [slashing](#slashing) section. //! //! ### Adminstration //! @@ -86,9 +86,6 @@ //! _Notes_: this section uses pseudo code to explain general design and does not necessarily //! reflect the exact implementation. Additionally, a working knowledge of `pallet-staking`'s api is //! assumed. -//! In order to maintain scalability, all operations are independent of the number of delegators. To -//! do this, we store delegation specific information local to the delegator while the pool data -//! structures have bounded datum. //! //! ### Goals //! @@ -305,6 +302,7 @@ // - Refactor staking slashing to always slash unlocking chunks (then back port) // - write detailed docs for StakingInterface // - various backports +// - Counter for delegators per pool and allow limiting of delegators per pool // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -772,6 +770,7 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: weights::WeightInfo; + // TODO: Should this just be part of the StakingInterface trait? We want the currencies to be the same anyways /// The nominating balance. type Currency: Currency; diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 56ba608a70eb5..d890a48981379 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -107,11 +107,13 @@ pub trait StakingInterface { fn unbond(controller: Self::AccountId, value: Self::Balance) -> DispatchResult; + /// Withdraw unbonded funds from bonded user. fn withdraw_unbonded( controller: Self::AccountId, num_slashing_spans: u32, ) -> Result; + /// Bond the funds and create a `stash` and `controller`, a bond of `value`, and `payee` account as the reward destination. fn bond( stash: Self::AccountId, controller: Self::AccountId, @@ -119,8 +121,9 @@ pub trait StakingInterface { payee: Self::AccountId, ) -> DispatchResult; + /// Have `controller` nominate `validators`. fn nominate( - stash: Self::AccountId, - targets: sp_std::vec::Vec, + controller: Self::AccountId, + validators: sp_std::vec::Vec, ) -> DispatchResult; } From ad575b7b7c5e565a17af97839641f165b38aabee Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 24 Feb 2022 15:35:53 -0800 Subject: [PATCH 134/299] Refactor ledger::slash --- frame/nomination-pools/src/lib.rs | 10 ++- frame/staking/src/lib.rs | 139 +++++++++++++----------------- frame/staking/src/mock.rs | 13 +-- frame/staking/src/pallet/mod.rs | 10 ++- primitives/staking/src/lib.rs | 127 +++++++++++++++++---------- 5 files changed, 156 insertions(+), 143 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index ce5ebdda56cd3..ef70696ac5d7d 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1,9 +1,10 @@ //! # Nomination Pools for Staking Delegation //! -//! This pallet allows delegators to delegate their stake to nominating pools, each of which acts as -//! a nominator and nominates validators on the delegators behalf. +//! A pallet that allows delegators to delegate their stake to nominating pools, each of which acts +//! as a nominator and nominates validators on the delegators behalf. +//! +//! # Index //! -//! Index: //! * [Key terms](#key-terms) //! * [Usage](#usage) //! * [Design](#design) @@ -770,7 +771,8 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: weights::WeightInfo; - // TODO: Should this just be part of the StakingInterface trait? We want the currencies to be the same anyways + // TODO: Should this just be part of the StakingInterface trait? We want the currencies to + // be the same anyways /// The nominating balance. type Currency: Currency; diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 6a6a1ee34d63f..d66ff0869c14f 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -315,7 +315,11 @@ use sp_staking::{ offence::{Offence, OffenceError, ReportOffence}, EraIndex, SessionIndex, }; -use sp_std::{collections::btree_map::BTreeMap, convert::From, prelude::*}; +use sp_std::{ + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + convert::From, + prelude::*, +}; pub use weights::WeightInfo; pub use pallet::{pallet::*, *}; @@ -521,107 +525,80 @@ impl StakingLedger { } /// Slash the staker for a given amount of balance. This can grow the value - /// of the slash in the case that the staker has less than `minimum_balance` - /// active funds. Returns the amount of funds actually slashed. + /// of the slash in the case that the staker has either active bonded or unlocking chunks that + /// become dust after slashing. /// - /// Slashes from `active` funds first, and then `unlocking`, starting with the - /// chunks that are closest to unlocking. + /// Note that this calls `Config::OnStakerSlash::on_slash` with information as to how the slash + /// was applied. fn slash( &mut self, - value: BalanceOf, - minimum_balance: BalanceOf, + staker_slash_amount: BalanceOf, + minimum_balance: BalanceOf, // TODO slash_era: EraIndex, active_era: EraIndex, ) -> BalanceOf { - use sp_staking::{PoolsInterface as _, SlashPoolArgs, SlashPoolOut}; - if let Some(SlashPoolOut { slashed_bonded, slashed_unlocking }) = - T::PoolsInterface::slash_pool(SlashPoolArgs { - pool_stash: &self.stash, - slash_amount: value, - slash_era, - apply_era: active_era, - active_bonded: self.active, - }) { - self.pool_slash(slashed_bonded, slashed_unlocking) - } else { - self.standard_slash(value, minimum_balance) - } - } - - // TODO look into how to decouple this - // move sensitive logic - /// Slash a pool account - fn pool_slash( - &mut self, - new_active: BalanceOf, - new_chunk_balances: BTreeMap>, - ) -> BalanceOf { - let mut total_slashed = BalanceOf::::zero(); + use sp_staking::OnStakerSlash as _; - // Modify the unlocking chunks in place - for chunk in &mut self.unlocking { - if let Some(new_balance) = new_chunk_balances.get(&chunk.era) { - let slashed_amount = chunk.value.saturating_sub(*new_balance); - self.total = self.total.saturating_sub(slashed_amount); - total_slashed = total_slashed.saturating_add(slashed_amount); + let pre_slash_total = self.total; - chunk.value = *new_balance; - } + // The range of eras that the staker could have unbonded in after the equivocation causing + // the slash. + let affected_range_set: BTreeSet<_> = ((slash_era + 1)..=active_era).collect(); + // Sum the balance of the affected chunks + let unbonding_affected_balance: BalanceOf = self + .unlocking + .iter() + .filter(|chunk| affected_range_set.contains(&chunk.era)) + .fold(BalanceOf::::zero(), |balance_sum, chunk| { + balance_sum.saturating_add(chunk.value) + }); + // Calculate the total affected balance + let total_affected_balance = self.active.saturating_add(unbonding_affected_balance); + if total_affected_balance.is_zero() { + // Exit early because there is nothing to slash + return Zero::zero() } - // Update the actively bonded - let slashed_amount = self.active.saturating_sub(new_active); - self.total = self.total.saturating_sub(slashed_amount); - self.active = new_active; - - total_slashed.saturating_add(slashed_amount) - } + // Helper to update `target` and the ledgers total after accounting for slashing `target`. + let do_proportional_slash = |target: &mut BalanceOf, total: &mut BalanceOf| { + if total_affected_balance.is_zero() { + // This should be checked for prior to calling, but just in case + return + } - // Slash a validator or nominator's stash - fn standard_slash( - &mut self, - mut value: BalanceOf, - minimum_balance: BalanceOf, - ) -> BalanceOf { - let pre_total = self.total; - let total = &mut self.total; - let active = &mut self.active; - - let slash_out_of = |total_remaining: &mut BalanceOf, - target: &mut BalanceOf, - value: &mut BalanceOf| { - let mut slash_from_target = (*value).min(*target); - - if !slash_from_target.is_zero() { - *target -= slash_from_target; - - // Don't leave a dust balance in the staking system. - if *target <= minimum_balance { - slash_from_target += *target; - *value += sp_std::mem::replace(target, Zero::zero()); - } + // Equivalent to `(slash_amount / total_affected_balance) * target`. + let slash_from_target = staker_slash_amount.saturating_mul(*target) + // Checked for zero above + / total_affected_balance; - *total_remaining = total_remaining.saturating_sub(slash_from_target); - *value -= slash_from_target; - } + *target = target.saturating_sub(slash_from_target); + let actual_slashed = if *target <= minimum_balance { + // Slash the entire target if its dust. + sp_std::mem::replace(target, Zero::zero()) + } else { + slash_from_target + }; + *total = total.saturating_sub(actual_slashed); }; - slash_out_of(total, active, &mut value); + // Slash the active balance + do_proportional_slash(&mut self.active, &mut self.total); - let i = self + let slashed_unlocking: BTreeMap<_, _> = self .unlocking .iter_mut() + .filter(|chunk| affected_range_set.contains(&chunk.era)) .map(|chunk| { - slash_out_of(total, &mut chunk.value, &mut value); - chunk.value + // Slash the chunk + do_proportional_slash(&mut chunk.value, &mut self.total); + + (chunk.era, chunk.value) }) - .take_while(|value| value.is_zero()) // Take all fully-consumed chunks out. - .count(); + .collect(); - // Kill all drained chunks. - let _ = self.unlocking.drain(..i); + T::OnStakerSlash::on_slash(&self.stash, self.active, &slashed_unlocking); - pre_total.saturating_sub(*total) + pre_slash_total.saturating_sub(self.total) } } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 38a20d7ce3102..c161f96bc6251 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -82,17 +82,6 @@ pub fn is_disabled(controller: AccountId) -> bool { Session::disabled_validators().contains(&validator_index) } -pub struct PoolsInterfaceMock; -impl sp_staking::PoolsInterface for PoolsInterfaceMock { - type AccountId = AccountId; - type Balance = Balance; - fn slash_pool( - _: sp_staking::SlashPoolArgs, - ) -> Option> { - None - } -} - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -283,7 +272,7 @@ impl crate::pallet::pallet::Config for Test { // NOTE: consider a macro and use `UseNominatorsMap` as well. type SortedListProvider = BagsList; type BenchmarkingConfig = TestBenchmarkingConfig; - type PoolsInterface = PoolsInterfaceMock; + type OnStakerSlash = (); type WeightInfo = (); } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 2fa441f57bd61..e3d7ba0af05f6 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -31,7 +31,7 @@ use sp_runtime::{ traits::{CheckedSub, SaturatedConversion, StaticLookup, Zero}, Perbill, Percent, }; -use sp_staking::{EraIndex, PoolsInterface, SessionIndex}; +use sp_staking::{EraIndex, SessionIndex}; use sp_std::{convert::From, prelude::*}; mod impls; @@ -156,7 +156,13 @@ pub mod pallet { /// the bags-list is not desired, [`impls::UseNominatorsMap`] is likely the desired option. type SortedListProvider: SortedListProvider; - type PoolsInterface: PoolsInterface>; + // type PoolsInterface: PoolsInterface>; + + // TODO: create a default impl of OnStakerSlash for staking + /// A hook called when any staker is slashed. Mostly likely this can be a no-op unless + /// there are delegation pools. + type OnStakerSlash: sp_staking::OnStakerSlash>; /// Some parameters of the benchmarking. type BenchmarkingConfig: BenchmarkingConfig; diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index d890a48981379..c4df8de17b0a8 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -30,53 +30,91 @@ pub type SessionIndex = u32; /// Counter for the number of eras that have passed. pub type EraIndex = u32; -/// Arguments for [`PoolsInterface::slash_pool`]. -pub struct SlashPoolArgs<'a, AccountId, Balance> { - /// _Stash_ account of the pool - pub pool_stash: &'a AccountId, - /// The amount to slash - pub slash_amount: Balance, - /// Era the slash happened in - pub slash_era: EraIndex, - /// Era the slash is applied in - pub apply_era: EraIndex, - /// The current active bonded of the account (i.e. `StakingLedger::active`) - pub active_bonded: Balance, +/// Trait for a hook for any operations to perform while a staker is being slashed. +pub trait OnStakerSlash { + /// A hook for any operations to perform while a staker is being slashed. + /// + /// # Arguments + /// + /// * `stash` - the staker whom the slash was applied to. + /// * `slashed_bonded` - The new bonded balance of the staker after the slash was applied. + /// * `slashed_unlocking` - A map from eras that the staker is unbonding in to the new balance + /// after the slash was applied. + fn on_slash( + stash: &AccountId, + slashed_bonded: Balance, + slashed_unlocking: &BTreeMap, + ); } -/// Output for [`PoolsInterface::slash_pool`]. -pub struct SlashPoolOut { - /// The new active bonded balance of the stash with the proportional slash amounted subtracted. - pub slashed_bonded: Balance, - /// A map from era of unlocking chunks to their new balance with the proportional slash amount - /// subtracted. - pub slashed_unlocking: BTreeMap, +impl OnStakerSlash for () { + fn on_slash(_: &AccountId, _: Balance, _: &BTreeMap) { + // Nothing to do here + } } -pub trait PoolsInterface { - type AccountId; - type Balance; - - // The current approach here is to share `BTreeMap>` with the staking - // API. This is arguably a leaky, suboptimal API because both sides have to share this - // non-trivial data structure. With the current design we do this because we track the unbonding - // balance in both the pallet-staking `unlocking` chunks and in here with the pallet-pools - // `SubPools`. Because both pallets need to know about slashes to unbonding funds we either have - // to replicate the slashing logic between the pallets, or share some data. A ALTERNATIVE is - // having the pallet-pools read the unbonding balance per era directly from pallet-staking. The - // downside of this is that once a delegator calls `withdraw_unbonded`, the chunk is removed and - // we can't keep track of the balance for that `UnbondPool` anymore, thus we must merge the - // balance and points of that `UnbondPool` with the `no_era` pool immediately upon calling - // withdraw_unbonded. We choose not to do this because if there was a slash, it would negatively - // affect the points:balance ratio of the `no_era` pool for everyone, including those who may - // not have been unbonding in eras effected by the slash. - /// Calculate the slashes for each unbonding chunk/unbonding pool and the actively bonded - /// balance. This should apply the updated balances to the pools and return the updated balances - /// to the caller (presumably pallet-staking) so they can do the corresponding updates. - fn slash_pool( - args: SlashPoolArgs, - ) -> Option>; -} +/// Arguments for [`PoolsInterface::slash_pool`]. +// pub struct AccountSlashCalcArgs<'a, AccountId, Balance> { +// /// _Stash_ account of the account to slash. +// pub pool_stash: &'a AccountId, +// /// The amount to slash. +// pub slash_amount: Balance, +// /// Era the slash happened in. +// pub slash_era: EraIndex, +// /// Era the slash is applied in. +// pub apply_era: EraIndex, +// /// The current active bonded of the account (i.e. `StakingLedger::active`) +// pub active_bonded: Balance, +// /// Unlocking funds +// pub unlocking: BTreeMap, +// } + +// /// Output for [`AccountSlashCalc::calc_slash_distribution`]. +// pub struct AccountSlashCalcOut { +// /// The new active bonded balance of the stash with the proportional slash amounted subtracted. +// pub slashed_bonded: Balance, +// /// A map from era of unlocking chunks to their new balance with the proportional slash amount +// /// subtracted. +// pub slashed_unlocking: BTreeMap, +// } + +/// Something that calculates the new balances of a staked account that has had a slash applied to +/// it. +// pub trait AccountSlashCalc { +// type AccountId; +// type Balance; + +// // The current approach here is to share `BTreeMap>` with the staking +// // API. This is arguably a leaky, suboptimal API because both sides have to share this +// // non-trivial data structure. With the current design we do this because we track the unbonding +// // balance in both the pallet-staking `unlocking` chunks and in here with the pallet-pools +// // `SubPools`. Because both pallets need to know about slashes to unbonding funds we either have +// // to replicate the slashing logic between the pallets, or share some data. A ALTERNATIVE is +// // having the pallet-pools read the unbonding balance per era directly from pallet-staking. The +// // downside of this is that once a delegator calls `withdraw_unbonded`, the chunk is removed and +// // we can't keep track of the balance for that `UnbondPool` anymore, thus we must merge the +// // balance and points of that `UnbondPool` with the `no_era` pool immediately upon calling +// // withdraw_unbonded. We choose not to do this because if there was a slash, it would negatively +// // affect the points:balance ratio of the `no_era` pool for everyone, including those who may +// // not have been unbonding in eras effected by the slash. +// /// Calculate the distribution of a slash for the given account. The distribution can be over +// /// the bonded balance of the account, and the unbonding balance per era. +// fn calc_slash_distribution( +// args: SlashPoolArgs, +// ) -> Option>; +// } + +// pub trait PoolsInterface { +// type AccountId; +// type Balance; + +// /// Calculate the slashes for each unbonding chunk/unbonding pool and the actively bonded +// /// balance. This should apply the updated balances to the pools and return the updated balances +// /// to the caller (presumably pallet-staking) so they can do the corresponding updates. +// fn slash_pool( +// args: SlashPoolArgs, +// ) -> Option>; +// } /// Trait for communication with the staking pallet. pub trait StakingInterface { @@ -113,7 +151,8 @@ pub trait StakingInterface { num_slashing_spans: u32, ) -> Result; - /// Bond the funds and create a `stash` and `controller`, a bond of `value`, and `payee` account as the reward destination. + /// Bond the funds and create a `stash` and `controller`, a bond of `value`, and `payee` account + /// as the reward destination. fn bond( stash: Self::AccountId, controller: Self::AccountId, From fe6b0891772bd0defe610d500cb0f847ad432917 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 24 Feb 2022 16:28:29 -0800 Subject: [PATCH 135/299] Add on_slash impl for nomination pools --- frame/nomination-pools/src/lib.rs | 128 +++++------------ frame/nomination-pools/src/tests.rs | 203 --------------------------- frame/staking/src/pallet/mod.rs | 4 - frame/staking/src/tests.rs | 210 +++++++++++++++++++++++++++- 4 files changed, 236 insertions(+), 309 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index ef70696ac5d7d..731db2bd30101 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -300,10 +300,9 @@ // a pool is flipped to a destroying state it cannot change its state. // TODO -// - Refactor staking slashing to always slash unlocking chunks (then back port) -// - write detailed docs for StakingInterface -// - various backports // - Counter for delegators per pool and allow limiting of delegators per pool +// - write detailed docs for StakingInterface +// - various back ports // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -319,7 +318,7 @@ use scale_info::TypeInfo; use sp_core::U256; use sp_io::hashing::blake2_256; use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZeroInput, Zero}; -use sp_staking::{EraIndex, PoolsInterface, SlashPoolArgs, SlashPoolOut, StakingInterface}; +use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div, vec::Vec}; #[cfg(test)] @@ -347,8 +346,9 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), + (true, true) | (false, true) => { + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) + }, (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -373,7 +373,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero() + return Zero::zero(); } // Equivalent of (current_balance / current_points) * delegator_points @@ -525,8 +525,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) < - BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) + < BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -702,7 +702,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self + return self; } // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -1117,8 +1117,8 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); ensure!( - current_era.saturating_sub(unbonding_era) >= - T::StakingInterface::bonding_duration(), + current_era.saturating_sub(unbonding_era) + >= T::StakingInterface::bonding_duration(), Error::::NotUnbondedYet ); @@ -1220,8 +1220,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() && - amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() + && amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1376,9 +1376,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() || - current_points.is_zero() || - reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() + || current_points.is_zero() + || reward_pool.balance.is_zero() { Zero::zero() } else { @@ -1437,85 +1437,23 @@ impl Pallet { Ok(()) } - - fn do_slash( - SlashPoolArgs { - pool_stash, - slash_amount, - slash_era, - apply_era, - active_bonded, - }: SlashPoolArgs::>, - ) -> Option>> { - // Make sure this is a pool account - BondedPools::::contains_key(&pool_stash).then(|| ())?; - let mut sub_pools = SubPoolsStorage::::get(pool_stash).unwrap_or_default(); - - let affected_range = (slash_era + 1)..=apply_era; - - // Note that this doesn't count the balance in the `no_era` pool - let unbonding_affected_balance: BalanceOf = - affected_range.clone().fold(BalanceOf::::zero(), |balance_sum, era| { - if let Some(unbond_pool) = sub_pools.with_era.get(&era) { - balance_sum.saturating_add(unbond_pool.balance) - } else { - balance_sum - } - }); - - // Note that the balances of the bonded pool and its affected sub-pools will saturated at - // zero if slash_amount > total_affected_balance - let total_affected_balance = active_bonded.saturating_add(unbonding_affected_balance); - if total_affected_balance.is_zero() { - return Some(SlashPoolOut { - slashed_bonded: Zero::zero(), - slashed_unlocking: Default::default(), - }) - } - let slashed_unlocking: BTreeMap<_, _> = affected_range - .filter_map(|era| { - if let Some(mut unbond_pool) = sub_pools.with_era.get_mut(&era) { - let after_slash_balance = { - // Equivalent to `(slash_amount / total_affected_balance) * - // unbond_pool.balance` - let pool_slash_amount = slash_amount - .saturating_mul(unbond_pool.balance) - // We check for zero above - .div(total_affected_balance); - - unbond_pool.balance.saturating_sub(pool_slash_amount) - }; - - unbond_pool.balance = after_slash_balance; - - Some((era, after_slash_balance)) - } else { - None - } - }) - .collect(); - SubPoolsStorage::::insert(pool_stash, sub_pools); - - // Equivalent to `(slash_amount / total_affected_balance) * active_bonded` - let slashed_bonded = { - let bonded_pool_slash_amount = slash_amount - .saturating_mul(active_bonded) - // We check for zero above - .div(total_affected_balance); - - active_bonded.saturating_sub(bonded_pool_slash_amount) - }; - Some(SlashPoolOut { slashed_bonded, slashed_unlocking }) - } } -impl PoolsInterface for Pallet { - type AccountId = T::AccountId; - type Balance = BalanceOf; - - fn slash_pool( - args: SlashPoolArgs, - ) -> Option> { - Self::do_slash(args) +impl OnStakerSlash> for Pallet { + fn on_slash( + pool_account: &T::AccountId, + _slashed_bonded: BalanceOf, + slashed_unlocking: &BTreeMap>, + ) { + let mut sub_pools = match SubPoolsStorage::::get(pool_account) { + Some(sub_pools) => sub_pools, + None => return, + }; + for (era, slashed_balance) in slashed_unlocking.iter() { + if let Some(pool) = sub_pools.with_era.get_mut(era) { + pool.balance = *slashed_balance + } + } + SubPoolsStorage::::insert(pool_account.clone(), sub_pools); } } diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 793d34f4b9572..5dd5e1e7862fb 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -2031,209 +2031,6 @@ mod create { } } -mod pools_interface { - use super::*; - - #[test] - fn slash_pool_works_in_simple_cases() { - // Slash with no sub pools - ExtBuilder::default().build_and_execute(|| { - // When - let SlashPoolOut { slashed_bonded, slashed_unlocking } = - Pools::slash_pool(SlashPoolArgs { - pool_stash: &PRIMARY_ACCOUNT, - slash_amount: 9, - slash_era: 0, - apply_era: 3, - active_bonded: 10, - }) - .unwrap(); - - // Then - assert_eq!( - SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), - Default::default() - ); - assert_eq!(slashed_unlocking, Default::default()); - assert_eq!(slashed_bonded, 1); - }); - - // Slash, but all sub pools are out of range - ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { - // Given - // Unbond in era 0 - assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); - - // When - let SlashPoolOut { slashed_bonded, slashed_unlocking } = - Pools::slash_pool(SlashPoolArgs { - pool_stash: &PRIMARY_ACCOUNT, - slash_amount: 9, - // We start slashing unbonding pools in `slash_era + 1` - slash_era: 0, - apply_era: 3, - active_bonded: 100, - }) - .unwrap(); - - // Then - assert_eq!( - SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), - SubPools { - no_era: Default::default(), - with_era: sub_pools_with_era! { - 0 => UnbondPool { points: 100, balance: 100 } - } - } - ); - assert_eq!(slashed_unlocking, Default::default()); - assert_eq!(slashed_bonded, 91); - }); - } - - // Some sub pools are in range of the slash while others are not. - #[test] - fn slash_pool_works_in_complex_cases() { - ExtBuilder::default() - .add_delegators(vec![(40, 40), (100, 100), (200, 200), (300, 300), (400, 400)]) - .build_and_execute(|| { - // Make sure no pools get merged into `SubPools::no_era` until era 7. - BondingDuration::set(5); - assert_eq!(TotalUnbondingPools::::get(), 7); - - assert_ok!(Pools::unbond_other(Origin::signed(400), 400)); - - CurrentEra::set(1); - assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); - - CurrentEra::set(3); - assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); - - CurrentEra::set(5); - assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); - - CurrentEra::set(6); - assert_ok!(Pools::unbond_other(Origin::signed(300), 300)); - - // Given - assert_eq!( - SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), - SubPools { - no_era: Default::default(), - with_era: sub_pools_with_era! { - 0 => UnbondPool { points: 400, balance: 400 }, - 1 => UnbondPool { points: 40, balance: 40 }, - 3 => UnbondPool { points: 100, balance: 100 }, - 5 => UnbondPool { points: 200, balance: 200 }, - 6 => UnbondPool { points: 300, balance: 300 }, - } - } - ); - - // When - let SlashPoolOut { slashed_bonded, slashed_unlocking } = - Pools::slash_pool(SlashPoolArgs { - pool_stash: &PRIMARY_ACCOUNT, - slash_amount: (40 + 100 + 200 + 10) / 2, - slash_era: 0, - apply_era: 5, - active_bonded: 10, - }) - .unwrap(); - - // Then - assert_eq!( - SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), - SubPools { - no_era: Default::default(), - with_era: sub_pools_with_era! { - 0 => UnbondPool { points: 400, balance: 400 }, - 1 => UnbondPool { points: 40, balance: 40 / 2 }, - 3 => UnbondPool { points: 100, balance: 100 / 2}, - 5 => UnbondPool { points: 200, balance: 200 / 2}, - 6 => UnbondPool { points: 300, balance: 300 }, - } - } - ); - let expected_slashed_unlocking: BTreeMap<_, _> = - [(1, 40 / 2), (3, 100 / 2), (5, 200 / 2)].into_iter().collect(); - assert_eq!(slashed_unlocking, expected_slashed_unlocking); - assert_eq!(slashed_bonded, 10 / 2); - }); - } - - // Same as above, but the slash amount is greater than the slash-able balance of the pool. - #[test] - fn pool_slash_works_with_slash_amount_greater_than_slashable() { - ExtBuilder::default() - .add_delegators(vec![(40, 40), (100, 100), (200, 200), (300, 300), (400, 400)]) - .build_and_execute(|| { - // Make sure no pools get merged into `SubPools::no_era` until era 7. - BondingDuration::set(5); - assert_eq!(TotalUnbondingPools::::get(), 7); - - assert_ok!(Pools::unbond_other(Origin::signed(400), 400)); - - CurrentEra::set(1); - assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); - - CurrentEra::set(3); - assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); - - CurrentEra::set(5); - assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); - - CurrentEra::set(6); - assert_ok!(Pools::unbond_other(Origin::signed(300), 300)); - - // Given - assert_eq!( - SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), - SubPools { - no_era: Default::default(), - with_era: sub_pools_with_era! { - 0 => UnbondPool { points: 400, balance: 400 }, - 1 => UnbondPool { points: 40, balance: 40 }, - 3 => UnbondPool { points: 100, balance: 100 }, - 5 => UnbondPool { points: 200, balance: 200 }, - 6 => UnbondPool { points: 300, balance: 300 }, - } - } - ); - - // When - let SlashPoolOut { slashed_bonded, slashed_unlocking } = - Pools::slash_pool(SlashPoolArgs { - pool_stash: &PRIMARY_ACCOUNT, - slash_amount: 40 + 100 + 200 + 400 + 10, - slash_era: 0, - apply_era: 5, - active_bonded: 10, - }) - .unwrap(); - - // Then - assert_eq!( - SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), - SubPools { - no_era: Default::default(), - with_era: sub_pools_with_era! { - 0 => UnbondPool { points: 400, balance: 400 }, - 1 => UnbondPool { points: 40, balance: 0 }, - 3 => UnbondPool { points: 100, balance: 0 }, - 5 => UnbondPool { points: 200, balance: 0 }, - 6 => UnbondPool { points: 300, balance: 300 }, - } - } - ); - let expected_slashed_unlocking: BTreeMap<_, _> = - [(1, 0), (3, 0), (5, 0)].into_iter().collect(); - assert_eq!(slashed_unlocking, expected_slashed_unlocking); - assert_eq!(slashed_bonded, 0); - }); - } -} - mod nominate { use super::*; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index e3d7ba0af05f6..a4e5c4225327f 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -156,10 +156,6 @@ pub mod pallet { /// the bags-list is not desired, [`impls::UseNominatorsMap`] is likely the desired option. type SortedListProvider: SortedListProvider; - // type PoolsInterface: PoolsInterface>; - - // TODO: create a default impl of OnStakerSlash for staking /// A hook called when any staker is slashed. Mostly likely this can be a no-op unless /// there are delegation pools. type OnStakerSlash: sp_staking::OnStakerSlash>; diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index bfe80a07b1d40..f9a2123b94114 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4747,10 +4747,206 @@ fn force_apply_min_commission_works() { }); } -mod staking_interface { - // use super::*; - // use sp_staking::StakingInterface as _; - - // TODO: probably should test all other fns of the interface impl? Although benchmarks should - // at least make sure those work on the happy path -} +// TODO: adapt these for the staking use case +// mod pools_interface { +// use super::*; + +// #[test] +// fn slash_pool_works_in_simple_cases() { +// // Slash with no sub pools +// ExtBuilder::default().build_and_execute(|| { +// // When +// let SlashPoolOut { slashed_bonded, slashed_unlocking } = +// Pools::slash_pool(SlashPoolArgs { +// pool_stash: &PRIMARY_ACCOUNT, +// slash_amount: 9, +// slash_era: 0, +// apply_era: 3, +// active_bonded: 10, +// }) +// .unwrap(); + +// // Then +// assert_eq!( +// SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), +// Default::default() +// ); +// assert_eq!(slashed_unlocking, Default::default()); +// assert_eq!(slashed_bonded, 1); +// }); + +// // Slash, but all sub pools are out of range +// ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { +// // Given +// // Unbond in era 0 +// assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); + +// // When +// let SlashPoolOut { slashed_bonded, slashed_unlocking } = +// Pools::slash_pool(SlashPoolArgs { +// pool_stash: &PRIMARY_ACCOUNT, +// slash_amount: 9, +// // We start slashing unbonding pools in `slash_era + 1` +// slash_era: 0, +// apply_era: 3, +// active_bonded: 100, +// }) +// .unwrap(); + +// // Then +// assert_eq!( +// SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), +// SubPools { +// no_era: Default::default(), +// with_era: sub_pools_with_era! { +// 0 => UnbondPool { points: 100, balance: 100 } +// } +// } +// ); +// assert_eq!(slashed_unlocking, Default::default()); +// assert_eq!(slashed_bonded, 91); +// }); +// } + +// // Some sub pools are in range of the slash while others are not. +// #[test] +// fn slash_pool_works_in_complex_cases() { +// ExtBuilder::default() +// .add_delegators(vec![(40, 40), (100, 100), (200, 200), (300, 300), (400, 400)]) +// .build_and_execute(|| { +// // Make sure no pools get merged into `SubPools::no_era` until era 7. +// BondingDuration::set(5); +// assert_eq!(TotalUnbondingPools::::get(), 7); + +// assert_ok!(Pools::unbond_other(Origin::signed(400), 400)); + +// CurrentEra::set(1); +// assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); + +// CurrentEra::set(3); +// assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); + +// CurrentEra::set(5); +// assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); + +// CurrentEra::set(6); +// assert_ok!(Pools::unbond_other(Origin::signed(300), 300)); + +// // Given +// assert_eq!( +// SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), +// SubPools { +// no_era: Default::default(), +// with_era: sub_pools_with_era! { +// 0 => UnbondPool { points: 400, balance: 400 }, +// 1 => UnbondPool { points: 40, balance: 40 }, +// 3 => UnbondPool { points: 100, balance: 100 }, +// 5 => UnbondPool { points: 200, balance: 200 }, +// 6 => UnbondPool { points: 300, balance: 300 }, +// } +// } +// ); + +// // When +// let SlashPoolOut { slashed_bonded, slashed_unlocking } = +// Pools::slash_pool(SlashPoolArgs { +// pool_stash: &PRIMARY_ACCOUNT, +// slash_amount: (40 + 100 + 200 + 10) / 2, +// slash_era: 0, +// apply_era: 5, +// active_bonded: 10, +// }) +// .unwrap(); + +// // Then +// assert_eq!( +// SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), +// SubPools { +// no_era: Default::default(), +// with_era: sub_pools_with_era! { +// 0 => UnbondPool { points: 400, balance: 400 }, +// 1 => UnbondPool { points: 40, balance: 40 / 2 }, +// 3 => UnbondPool { points: 100, balance: 100 / 2}, +// 5 => UnbondPool { points: 200, balance: 200 / 2}, +// 6 => UnbondPool { points: 300, balance: 300 }, +// } +// } +// ); +// let expected_slashed_unlocking: BTreeMap<_, _> = +// [(1, 40 / 2), (3, 100 / 2), (5, 200 / 2)].into_iter().collect(); +// assert_eq!(slashed_unlocking, expected_slashed_unlocking); +// assert_eq!(slashed_bonded, 10 / 2); +// }); +// } + +// // Same as above, but the slash amount is greater than the slash-able balance of the pool. +// #[test] +// fn pool_slash_works_with_slash_amount_greater_than_slashable() { +// ExtBuilder::default() +// .add_delegators(vec![(40, 40), (100, 100), (200, 200), (300, 300), (400, 400)]) +// .build_and_execute(|| { +// // Make sure no pools get merged into `SubPools::no_era` until era 7. +// BondingDuration::set(5); +// assert_eq!(TotalUnbondingPools::::get(), 7); + +// assert_ok!(Pools::unbond_other(Origin::signed(400), 400)); + +// CurrentEra::set(1); +// assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); + +// CurrentEra::set(3); +// assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); + +// CurrentEra::set(5); +// assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); + +// CurrentEra::set(6); +// assert_ok!(Pools::unbond_other(Origin::signed(300), 300)); + +// // Given +// assert_eq!( +// SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), +// SubPools { +// no_era: Default::default(), +// with_era: sub_pools_with_era! { +// 0 => UnbondPool { points: 400, balance: 400 }, +// 1 => UnbondPool { points: 40, balance: 40 }, +// 3 => UnbondPool { points: 100, balance: 100 }, +// 5 => UnbondPool { points: 200, balance: 200 }, +// 6 => UnbondPool { points: 300, balance: 300 }, +// } +// } +// ); + +// // When +// let SlashPoolOut { slashed_bonded, slashed_unlocking } = +// Pools::slash_pool(SlashPoolArgs { +// pool_stash: &PRIMARY_ACCOUNT, +// slash_amount: 40 + 100 + 200 + 400 + 10, +// slash_era: 0, +// apply_era: 5, +// active_bonded: 10, +// }) +// .unwrap(); + +// // Then +// assert_eq!( +// SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), +// SubPools { +// no_era: Default::default(), +// with_era: sub_pools_with_era! { +// 0 => UnbondPool { points: 400, balance: 400 }, +// 1 => UnbondPool { points: 40, balance: 0 }, +// 3 => UnbondPool { points: 100, balance: 0 }, +// 5 => UnbondPool { points: 200, balance: 0 }, +// 6 => UnbondPool { points: 300, balance: 300 }, +// } +// } +// ); +// let expected_slashed_unlocking: BTreeMap<_, _> = +// [(1, 0), (3, 0), (5, 0)].into_iter().collect(); +// assert_eq!(slashed_unlocking, expected_slashed_unlocking); +// assert_eq!(slashed_bonded, 0); +// }); +// } +// } \ No newline at end of file From 06bce1a623cdda5e6ac31de9d3c99cebd8af8ed9 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 24 Feb 2022 20:09:31 -0800 Subject: [PATCH 136/299] slash refactor wip --- frame/staking/src/lib.rs | 151 ++++++++++++++++++++++---------- frame/staking/src/slashing.rs | 5 ++ frame/staking/src/tests.rs | 160 ++++++++++++++++++---------------- 3 files changed, 193 insertions(+), 123 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index d66ff0869c14f..5bbe991b85556 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -517,7 +517,7 @@ impl StakingLedger { } if unlocking_balance >= value { - break + break; } } @@ -525,76 +525,131 @@ impl StakingLedger { } /// Slash the staker for a given amount of balance. This can grow the value - /// of the slash in the case that the staker has either active bonded or unlocking chunks that - /// become dust after slashing. + /// of the slash in the case that either the active bonded or some unlocking chunks become dust after slashing. + /// Returns the amount of funds actually slashed. /// /// Note that this calls `Config::OnStakerSlash::on_slash` with information as to how the slash /// was applied. + // + // Slashes are computed and executed by: + // + // 1) Balances of the unlocking chunks in range `slash_era + 1..=apply_era` are summed and stored in `total_balance_affected`. + // 2) `slash_ratio` is computed as `slash_amount / total_balance_affected`. + // 3) `Ledger::active` is set to `(1- slash_ratio) * Ledger::active`. + // 4) For all unlocking chunks in range `slash_era + 1..=apply_era` set their balance to `(1 - slash_ratio) * unbonding_pool_balance`. fn slash( &mut self, - staker_slash_amount: BalanceOf, - minimum_balance: BalanceOf, // TODO + slash_amount: BalanceOf, + minimum_balance: BalanceOf, slash_era: EraIndex, active_era: EraIndex, ) -> BalanceOf { use sp_staking::OnStakerSlash as _; + let remaining_slash = slash_amount; let pre_slash_total = self.total; - // The range of eras that the staker could have unbonded in after the equivocation causing - // the slash. - let affected_range_set: BTreeSet<_> = ((slash_era + 1)..=active_era).collect(); - // Sum the balance of the affected chunks - let unbonding_affected_balance: BalanceOf = self - .unlocking - .iter() - .filter(|chunk| affected_range_set.contains(&chunk.era)) - .fold(BalanceOf::::zero(), |balance_sum, chunk| { - balance_sum.saturating_add(chunk.value) - }); - // Calculate the total affected balance - let total_affected_balance = self.active.saturating_add(unbonding_affected_balance); - if total_affected_balance.is_zero() { + // The index of the first chunk after the slash + let start_index = self.unlocking.partition_point(|c| c.era < slash_era); + // The indices of from the first chunk after the slash up through the most recent chunk + let affected_indices = (start_index..self.unlocking.len()); + + // Calculate the total balance of active funds and unlocking funds in the affected range. + let affected_balance = { + let unbonding_affected_balance = affected_indices + .clone() + .filter_map(|i| self.unlocking.get_mut(i)) + .fold(Zero::zero(), |sum, chunk| { + sum.saturating_add(chunk.balance); + }); + self.active.saturating_add(unbonding_affected_balance) + }; + + if affected_balance.is_zero() { // Exit early because there is nothing to slash - return Zero::zero() + return Zero::zero(); } + // let account_for_slash = |target: &mut BalanceOf, total: &mut BalanceOf, slash_remaining: &mut Balance, slash_from_target| { + // *target = target.saturating_sub(slash_from_target); // 8 + // let actual_slashed = if *target <= minimum_balance { + // // Slash the rest of the target if its dust. + // sp_std::mem::replace(target, Zero::zero()) + slash_from_target + // } else { + // slash_from_target + // }; + + // *total = total.saturating_sub(actual_slashed); + // *slash_remaining = slash_remaining.saturating_sub(actual_slashed); + // }; // Helper to update `target` and the ledgers total after accounting for slashing `target`. - let do_proportional_slash = |target: &mut BalanceOf, total: &mut BalanceOf| { - if total_affected_balance.is_zero() { - // This should be checked for prior to calling, but just in case - return - } - - // Equivalent to `(slash_amount / total_affected_balance) * target`. - let slash_from_target = staker_slash_amount.saturating_mul(*target) - // Checked for zero above - / total_affected_balance; + let slash_proportion_out_of = + |target: &mut BalanceOf, total: &mut BalanceOf, slash_remaining: &mut Balance| { + // Equivalent to `(slash_amount / target) * target`. + let slash_from_target = slash_amount + .saturating_mul(*target) + // Checked for zero above + .div(affected_balance); // 2 + // account_for_slash(target, total, slash_remaining, slash_from_target) + *target = target.saturating_sub(slash_from_target); // 8 + let actual_slashed = if *target <= minimum_balance { + // Slash the rest of the target if its dust. + sp_std::mem::replace(target, Zero::zero()) + slash_from_target + } else { + slash_from_target + }; - *target = target.saturating_sub(slash_from_target); - let actual_slashed = if *target <= minimum_balance { - // Slash the entire target if its dust. - sp_std::mem::replace(target, Zero::zero()) - } else { - slash_from_target + *total = total.saturating_sub(actual_slashed); + *slash_remaining = slash_remaining.saturating_sub(actual_slashed); + }; + let slash_all_out_of = + |target: &mut Balance, total_remaining: &mut Balance, slash_remaining: &mut Balance| { + let mut slash_from_target = (*slash_remaining).min(*target); // target 8, remaining 2, slash_from 2 + + // *target = target.saturating_sub(slash_from_target); + // let actual_slashed = if *target <= minimum_balance { + // // Slash the rest of the target if its dust. + // sp_std::mem::replace(target, Zero::zero()) + slash_from_target + // } else { + // slash_from_target + // }; + + if !slash_from_target.is_zero() { + *target -= slash_from_target; // 6 + + // Don't leave a dust balance in the staking system. + if *target <= minimum_balance { + slash_from_target += *target; + *slash_remaining += sp_std::mem::replace(target, Zero::zero()); + } + + *total_remaining = total_remaining.saturating_sub(slash_from_target); + *slash_remaining -= slash_from_target; + } }; - *total = total.saturating_sub(actual_slashed); + + let + + let (slash_out_of, indices_to_slash) = if total_affected_balance <= slash_amount { + // The slash amount is + (slash_proportion_out_of, affected_indices) + } else { + (slash_all_out_of, affected_indices.chain((0..start_index).rev())) }; // Slash the active balance - do_proportional_slash(&mut self.active, &mut self.total); + slash_out_of(&mut self.active, &mut self.total, &mut remaining_slash); - let slashed_unlocking: BTreeMap<_, _> = self - .unlocking - .iter_mut() - .filter(|chunk| affected_range_set.contains(&chunk.era)) - .map(|chunk| { - // Slash the chunk - do_proportional_slash(&mut chunk.value, &mut self.total); + let mut slashed_unlocking = BTreeMap::<_, _>::new(); + // Slash the chunks we know are in the affected range + for chunk in indices_to_slash.filter_map(|i| self.unlocking.get_mut(i)) { + do_slash(&mut chunk.value, &mut self.total); + slashed_unlocking.insert(chunk.era, chunk.value); - (chunk.era, chunk.value) - }) - .collect(); + if remaining_slash.is_zero() { + break; + } + } T::OnStakerSlash::on_slash(&self.stash, self.active, &slashed_unlocking); diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 0a536a92e161e..ce1ba92e03ab8 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -614,6 +614,11 @@ pub fn do_slash( let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era, apply_era); if !value.is_zero() { + // TODO: if this happens the UnbondPools that did not get slashed could think + // there are more funds unbonding then there really is, which could lead to attempts + // at over-withdraw when a delegator goes to withdraw unbonded + // to solve this, when the go to withdraw unbonded, we can min the withdraw amount + // with the non locked balance. let (imbalance, missing) = T::Currency::slash(stash, value); slashed_imbalance.subsume(imbalance); diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index f9a2123b94114..026cea5e23919 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -275,9 +275,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 + - part_for_100_from_10 * total_payout_0 * 2 / 3 + - part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * total_payout_0 * 2 / 3 + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -313,9 +313,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 + - part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) + - part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -3967,8 +3967,8 @@ mod election_data_provider { #[test] fn targets_2sec_block() { let mut validators = 1000; - while ::WeightInfo::get_npos_targets(validators) < - 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_targets(validators) + < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { validators += 1; } @@ -3985,8 +3985,8 @@ mod election_data_provider { let slashing_spans = validators; let mut nominators = 1000; - while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) < - 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) + < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { nominators += 1; } @@ -4057,8 +4057,8 @@ mod election_data_provider { .build_and_execute(|| { // sum of all nominators who'd be voters (1), plus the self-votes (4). assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 5 ); @@ -4097,8 +4097,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 7 ); @@ -4142,8 +4142,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 6 ); @@ -4748,65 +4748,75 @@ fn force_apply_min_commission_works() { } // TODO: adapt these for the staking use case -// mod pools_interface { -// use super::*; -// #[test] -// fn slash_pool_works_in_simple_cases() { -// // Slash with no sub pools -// ExtBuilder::default().build_and_execute(|| { -// // When -// let SlashPoolOut { slashed_bonded, slashed_unlocking } = -// Pools::slash_pool(SlashPoolArgs { -// pool_stash: &PRIMARY_ACCOUNT, -// slash_amount: 9, -// slash_era: 0, -// apply_era: 3, -// active_bonded: 10, -// }) -// .unwrap(); - -// // Then -// assert_eq!( -// SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), -// Default::default() -// ); -// assert_eq!(slashed_unlocking, Default::default()); -// assert_eq!(slashed_bonded, 1); -// }); - -// // Slash, but all sub pools are out of range -// ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { -// // Given -// // Unbond in era 0 -// assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); - -// // When -// let SlashPoolOut { slashed_bonded, slashed_unlocking } = -// Pools::slash_pool(SlashPoolArgs { -// pool_stash: &PRIMARY_ACCOUNT, -// slash_amount: 9, -// // We start slashing unbonding pools in `slash_era + 1` -// slash_era: 0, -// apply_era: 3, -// active_bonded: 100, -// }) -// .unwrap(); - -// // Then -// assert_eq!( -// SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), -// SubPools { -// no_era: Default::default(), -// with_era: sub_pools_with_era! { -// 0 => UnbondPool { points: 100, balance: 100 } -// } -// } -// ); -// assert_eq!(slashed_unlocking, Default::default()); -// assert_eq!(slashed_bonded, 91); -// }); -// } +#[test] +fn ledger_slash_works_in_simple_cases() { + // Given no unlocking chunks + let ledger = StakingLedger:: { + stash: 123, + total: 15, + active: 10, + unlocking: vec![], + claimed_rewards: vec![], + }; + + // When + assert_eq!(ledger.slash(5, 1, 0, 10), 0); + + // Then + assert_eq!(ledger.total, 10); + assert_eq!(ledger.active, 5); + + // When the slash amount is greater then the total + assert_eq!(ledger.slash(11, 1, 0, 10), 1); + + // Then + assert_eq!(ledger.total, 0); + assert_eq!(ledger.active, 0); + + // Given + let chunks = [0, 1, 8, 9].iter().map(|era| UnlockChunks { era, value: 10 }); + ledger.total = 4 * 10; + ledger.chunks = chunks.clone(); + + // When no chunks overlap with the slash eras, + assert_eq!(ledger.slash(10, 1, 1, 7), 10); + + // Then + assert_eq(ledger.total, 4 * 10); + + // // Slash, but all sub pools are out of range + // ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { + // // Given + // // Unbond in era 0 + // assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); + + // // When + // let SlashPoolOut { slashed_bonded, slashed_unlocking } = + // Pools::slash_pool(SlashPoolArgs { + // pool_stash: &PRIMARY_ACCOUNT, + // slash_amount: 9, + // // We start slashing unbonding pools in `slash_era + 1` + // slash_era: 0, + // apply_era: 3, + // active_bonded: 100, + // }) + // .unwrap(); + + // // Then + // assert_eq!( + // SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), + // SubPools { + // no_era: Default::default(), + // with_era: sub_pools_with_era! { + // 0 => UnbondPool { points: 100, balance: 100 } + // } + // } + // ); + // assert_eq!(slashed_unlocking, Default::default()); + // assert_eq!(slashed_bonded, 91); + // }); +} // // Some sub pools are in range of the slash while others are not. // #[test] @@ -4949,4 +4959,4 @@ fn force_apply_min_commission_works() { // assert_eq!(slashed_bonded, 0); // }); // } -// } \ No newline at end of file +// } From 4bb4850f65cdf7f5bfa08e809045bca61c817be3 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 24 Feb 2022 20:49:27 -0800 Subject: [PATCH 137/299] WIP slash working --- frame/staking/src/lib.rs | 111 +++++++++++++++++++++++-------------- frame/staking/src/tests.rs | 64 ++++++++++----------- 2 files changed, 100 insertions(+), 75 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 5bbe991b85556..c93fd7b40dd12 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -542,25 +542,28 @@ impl StakingLedger { slash_amount: BalanceOf, minimum_balance: BalanceOf, slash_era: EraIndex, - active_era: EraIndex, + _active_era: EraIndex, // TODO ) -> BalanceOf { use sp_staking::OnStakerSlash as _; + use sp_std::ops::Div; - let remaining_slash = slash_amount; + let mut remaining_slash = slash_amount; let pre_slash_total = self.total; // The index of the first chunk after the slash let start_index = self.unlocking.partition_point(|c| c.era < slash_era); // The indices of from the first chunk after the slash up through the most recent chunk - let affected_indices = (start_index..self.unlocking.len()); + let affected_indices = start_index..self.unlocking.len(); // Calculate the total balance of active funds and unlocking funds in the affected range. let affected_balance = { - let unbonding_affected_balance = affected_indices - .clone() - .filter_map(|i| self.unlocking.get_mut(i)) - .fold(Zero::zero(), |sum, chunk| { - sum.saturating_add(chunk.balance); + let unbonding_affected_balance = + affected_indices.clone().fold(BalanceOf::::zero(), |sum, i| { + if let Some(chunk) = self.unlocking.get_mut(i) { + sum.saturating_add(chunk.value) + } else { + sum + } }); self.active.saturating_add(unbonding_affected_balance) }; @@ -570,7 +573,8 @@ impl StakingLedger { return Zero::zero(); } - // let account_for_slash = |target: &mut BalanceOf, total: &mut BalanceOf, slash_remaining: &mut Balance, slash_from_target| { + // let do_accounting = |target: &mut BalanceOf, total: &mut BalanceOf, slash_remaining: &mut Balance, calc_slash| { + // let calc_slash() // *target = target.saturating_sub(slash_from_target); // 8 // let actual_slashed = if *target <= minimum_balance { // // Slash the rest of the target if its dust. @@ -582,15 +586,18 @@ impl StakingLedger { // *total = total.saturating_sub(actual_slashed); // *slash_remaining = slash_remaining.saturating_sub(actual_slashed); // }; + // Helper to update `target` and the ledgers total after accounting for slashing `target`. let slash_proportion_out_of = - |target: &mut BalanceOf, total: &mut BalanceOf, slash_remaining: &mut Balance| { + |target: &mut BalanceOf, + total: &mut BalanceOf, + slash_remaining: &mut BalanceOf| { // Equivalent to `(slash_amount / target) * target`. let slash_from_target = slash_amount .saturating_mul(*target) // Checked for zero above .div(affected_balance); // 2 - // account_for_slash(target, total, slash_remaining, slash_from_target) + *target = target.saturating_sub(slash_from_target); // 8 let actual_slashed = if *target <= minimum_balance { // Slash the rest of the target if its dust. @@ -602,51 +609,69 @@ impl StakingLedger { *total = total.saturating_sub(actual_slashed); *slash_remaining = slash_remaining.saturating_sub(actual_slashed); }; - let slash_all_out_of = - |target: &mut Balance, total_remaining: &mut Balance, slash_remaining: &mut Balance| { - let mut slash_from_target = (*slash_remaining).min(*target); // target 8, remaining 2, slash_from 2 - - // *target = target.saturating_sub(slash_from_target); - // let actual_slashed = if *target <= minimum_balance { - // // Slash the rest of the target if its dust. - // sp_std::mem::replace(target, Zero::zero()) + slash_from_target - // } else { - // slash_from_target - // }; - - if !slash_from_target.is_zero() { - *target -= slash_from_target; // 6 - - // Don't leave a dust balance in the staking system. - if *target <= minimum_balance { - slash_from_target += *target; - *slash_remaining += sp_std::mem::replace(target, Zero::zero()); - } - - *total_remaining = total_remaining.saturating_sub(slash_from_target); - *slash_remaining -= slash_from_target; + let slash_all_out_of = |target: &mut BalanceOf, + total_remaining: &mut BalanceOf, + slash_remaining: &mut BalanceOf| { + let mut slash_from_target = (*slash_remaining).min(*target); // target 8, remaining 2, slash_from 2 + + // *target = target.saturating_sub(slash_from_target); + // let actual_slashed = if *target <= minimum_balance { + // // Slash the rest of the target if its dust. + // sp_std::mem::replace(target, Zero::zero()) + slash_from_target + // } else { + // slash_from_target + // }; + + if !slash_from_target.is_zero() { + *target -= slash_from_target; // 6 + + // Don't leave a dust balance in the staking system. + if *target <= minimum_balance { + slash_from_target += *target; + *slash_remaining += sp_std::mem::replace(target, Zero::zero()); } - }; - let + *total_remaining = total_remaining.saturating_sub(slash_from_target); + *slash_remaining -= slash_from_target; + }; + }; - let (slash_out_of, indices_to_slash) = if total_affected_balance <= slash_amount { + let slash_out_of = if affected_balance <= slash_amount { // The slash amount is - (slash_proportion_out_of, affected_indices) + Box::new(slash_proportion_out_of) + as Box, &mut BalanceOf, &mut BalanceOf) -> ()> + } else { + Box::new(slash_all_out_of) + as Box, &mut BalanceOf, &mut BalanceOf) -> ()> + }; + let indices_to_slash = if affected_balance <= slash_amount { + Box::new(affected_indices) as Box> } else { - (slash_all_out_of, affected_indices.chain((0..start_index).rev())) + Box::new(affected_indices.chain((0..start_index).rev())) + as Box> }; + // let (slash_out_of, indices_to_slash) = if affected_balance <= slash_amount { + // // The slash amount is + // (Box::new(slash_proportion_out_of), Box::new(affected_indices)) + // } else { + // (Box::new(affected_indices.chain((0..start_index).rev()) + // }; + // Slash the active balance slash_out_of(&mut self.active, &mut self.total, &mut remaining_slash); let mut slashed_unlocking = BTreeMap::<_, _>::new(); // Slash the chunks we know are in the affected range - for chunk in indices_to_slash.filter_map(|i| self.unlocking.get_mut(i)) { - do_slash(&mut chunk.value, &mut self.total); - slashed_unlocking.insert(chunk.era, chunk.value); + for i in indices_to_slash { + if let Some(chunk) = self.unlocking.get_mut(i) { + slash_out_of(&mut chunk.value, &mut self.total, &mut remaining_slash); + slashed_unlocking.insert(chunk.era, chunk.value); - if remaining_slash.is_zero() { + if remaining_slash.is_zero() { + break; + } + } else { break; } } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 026cea5e23919..8b0473c0601c2 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4752,38 +4752,38 @@ fn force_apply_min_commission_works() { #[test] fn ledger_slash_works_in_simple_cases() { // Given no unlocking chunks - let ledger = StakingLedger:: { - stash: 123, - total: 15, - active: 10, - unlocking: vec![], - claimed_rewards: vec![], - }; - - // When - assert_eq!(ledger.slash(5, 1, 0, 10), 0); - - // Then - assert_eq!(ledger.total, 10); - assert_eq!(ledger.active, 5); - - // When the slash amount is greater then the total - assert_eq!(ledger.slash(11, 1, 0, 10), 1); - - // Then - assert_eq!(ledger.total, 0); - assert_eq!(ledger.active, 0); - - // Given - let chunks = [0, 1, 8, 9].iter().map(|era| UnlockChunks { era, value: 10 }); - ledger.total = 4 * 10; - ledger.chunks = chunks.clone(); - - // When no chunks overlap with the slash eras, - assert_eq!(ledger.slash(10, 1, 1, 7), 10); - - // Then - assert_eq(ledger.total, 4 * 10); + // let ledger = StakingLedger:: { + // stash: 123, + // total: 15, + // active: 10, + // unlocking: vec![], + // claimed_rewards: vec![], + // }; + + // // When + // assert_eq!(ledger.slash(5, 1, 0, 10), 0); + + // // Then + // assert_eq!(ledger.total, 10); + // assert_eq!(ledger.active, 5); + + // // When the slash amount is greater then the total + // assert_eq!(ledger.slash(11, 1, 0, 10), 1); + + // // Then + // assert_eq!(ledger.total, 0); + // assert_eq!(ledger.active, 0); + + // // Given + // let chunks = [0, 1, 8, 9].iter().map(|era| UnlockChunks { era, value: 10 }); + // ledger.total = 4 * 10; + // ledger.chunks = chunks.clone(); + + // // When no chunks overlap with the slash eras, + // assert_eq!(ledger.slash(10, 1, 1, 7), 10); + + // // Then + // assert_eq(ledger.total, 4 * 10); // // Slash, but all sub pools are out of range // ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { From 370d46864debbb6bf488207b023b836a2c11febe Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 24 Feb 2022 21:44:47 -0800 Subject: [PATCH 138/299] DRY Ledger::stash --- frame/nomination-pools/src/lib.rs | 27 +++---- frame/staking/src/lib.rs | 128 ++++++++++-------------------- frame/staking/src/tests.rs | 32 ++++---- 3 files changed, 69 insertions(+), 118 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 731db2bd30101..831cc58842971 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -346,9 +346,8 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => { - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) - }, + (true, true) | (false, true) => + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -373,7 +372,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero(); + return Zero::zero() } // Equivalent of (current_balance / current_points) * delegator_points @@ -525,8 +524,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) - < BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) < + BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -702,7 +701,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self; + return self } // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -1117,8 +1116,8 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); ensure!( - current_era.saturating_sub(unbonding_era) - >= T::StakingInterface::bonding_duration(), + current_era.saturating_sub(unbonding_era) >= + T::StakingInterface::bonding_duration(), Error::::NotUnbondedYet ); @@ -1220,8 +1219,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() - && amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() && + amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1376,9 +1375,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() - || current_points.is_zero() - || reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() || + current_points.is_zero() || + reward_pool.balance.is_zero() { Zero::zero() } else { diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index c93fd7b40dd12..89110cb3d260d 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -517,7 +517,7 @@ impl StakingLedger { } if unlocking_balance >= value { - break; + break } } @@ -525,18 +525,19 @@ impl StakingLedger { } /// Slash the staker for a given amount of balance. This can grow the value - /// of the slash in the case that either the active bonded or some unlocking chunks become dust after slashing. - /// Returns the amount of funds actually slashed. + /// of the slash in the case that either the active bonded or some unlocking chunks become dust + /// after slashing. Returns the amount of funds actually slashed. /// /// Note that this calls `Config::OnStakerSlash::on_slash` with information as to how the slash /// was applied. // // Slashes are computed and executed by: // - // 1) Balances of the unlocking chunks in range `slash_era + 1..=apply_era` are summed and stored in `total_balance_affected`. - // 2) `slash_ratio` is computed as `slash_amount / total_balance_affected`. - // 3) `Ledger::active` is set to `(1- slash_ratio) * Ledger::active`. - // 4) For all unlocking chunks in range `slash_era + 1..=apply_era` set their balance to `(1 - slash_ratio) * unbonding_pool_balance`. + // 1) Balances of the unlocking chunks in range `slash_era + 1..=apply_era` are summed and + // stored in `total_balance_affected`. 2) `slash_ratio` is computed as `slash_amount / + // total_balance_affected`. 3) `Ledger::active` is set to `(1- slash_ratio) * Ledger::active`. + // 4) For all unlocking chunks in range `slash_era + 1..=apply_era` set their balance to `(1 - + // slash_ratio) * unbonding_pool_balance`. fn slash( &mut self, slash_amount: BalanceOf, @@ -552,7 +553,8 @@ impl StakingLedger { // The index of the first chunk after the slash let start_index = self.unlocking.partition_point(|c| c.era < slash_era); - // The indices of from the first chunk after the slash up through the most recent chunk + // The indices of from the first chunk after the slash up through the most recent chunk. + // (The most recent chunk is at greatest from this era) let affected_indices = start_index..self.unlocking.len(); // Calculate the total balance of active funds and unlocking funds in the affected range. @@ -570,109 +572,59 @@ impl StakingLedger { if affected_balance.is_zero() { // Exit early because there is nothing to slash - return Zero::zero(); + return Zero::zero() } - - // let do_accounting = |target: &mut BalanceOf, total: &mut BalanceOf, slash_remaining: &mut Balance, calc_slash| { - // let calc_slash() - // *target = target.saturating_sub(slash_from_target); // 8 - // let actual_slashed = if *target <= minimum_balance { - // // Slash the rest of the target if its dust. - // sp_std::mem::replace(target, Zero::zero()) + slash_from_target - // } else { - // slash_from_target - // }; - - // *total = total.saturating_sub(actual_slashed); - // *slash_remaining = slash_remaining.saturating_sub(actual_slashed); - // }; + // Wether or not this slash can be applied to just the active and affected unbonding chunks. + // If not, we have to slash all of the aforementioned and then continue slashing older + // unlocking chunks + let is_proportional_slash = affected_balance <= slash_amount; // Helper to update `target` and the ledgers total after accounting for slashing `target`. - let slash_proportion_out_of = - |target: &mut BalanceOf, - total: &mut BalanceOf, - slash_remaining: &mut BalanceOf| { - // Equivalent to `(slash_amount / target) * target`. - let slash_from_target = slash_amount + let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { + // Calculate the amount to slash from the target + let slash_from_target = if is_proportional_slash { + // Equivalent to `(slash_amount / affected_balance) * target`. + slash_amount .saturating_mul(*target) // Checked for zero above - .div(affected_balance); // 2 + .div(affected_balance) + } else { + (*slash_remaining).min(*target) + }; - *target = target.saturating_sub(slash_from_target); // 8 - let actual_slashed = if *target <= minimum_balance { - // Slash the rest of the target if its dust. - sp_std::mem::replace(target, Zero::zero()) + slash_from_target - } else { - slash_from_target - }; + *target = target.saturating_sub(slash_from_target); - *total = total.saturating_sub(actual_slashed); - *slash_remaining = slash_remaining.saturating_sub(actual_slashed); + let actual_slashed = if *target <= minimum_balance { + // Slash the rest of the target if its dust + sp_std::mem::replace(target, Zero::zero()) + slash_from_target + } else { + slash_from_target }; - let slash_all_out_of = |target: &mut BalanceOf, - total_remaining: &mut BalanceOf, - slash_remaining: &mut BalanceOf| { - let mut slash_from_target = (*slash_remaining).min(*target); // target 8, remaining 2, slash_from 2 - - // *target = target.saturating_sub(slash_from_target); - // let actual_slashed = if *target <= minimum_balance { - // // Slash the rest of the target if its dust. - // sp_std::mem::replace(target, Zero::zero()) + slash_from_target - // } else { - // slash_from_target - // }; - - if !slash_from_target.is_zero() { - *target -= slash_from_target; // 6 - - // Don't leave a dust balance in the staking system. - if *target <= minimum_balance { - slash_from_target += *target; - *slash_remaining += sp_std::mem::replace(target, Zero::zero()); - } - *total_remaining = total_remaining.saturating_sub(slash_from_target); - *slash_remaining -= slash_from_target; - }; + self.total = self.total.saturating_sub(actual_slashed); + *slash_remaining = slash_remaining.saturating_sub(actual_slashed); }; - let slash_out_of = if affected_balance <= slash_amount { - // The slash amount is - Box::new(slash_proportion_out_of) - as Box, &mut BalanceOf, &mut BalanceOf) -> ()> - } else { - Box::new(slash_all_out_of) - as Box, &mut BalanceOf, &mut BalanceOf) -> ()> - }; - let indices_to_slash = if affected_balance <= slash_amount { + // If this is *not* a proportional slash, the active will always wiped to 0. + slash_out_of(&mut self.active, &mut remaining_slash); + + let mut slashed_unlocking = BTreeMap::<_, _>::new(); + let indices_to_slash = if is_proportional_slash { Box::new(affected_indices) as Box> } else { Box::new(affected_indices.chain((0..start_index).rev())) as Box> }; - - // let (slash_out_of, indices_to_slash) = if affected_balance <= slash_amount { - // // The slash amount is - // (Box::new(slash_proportion_out_of), Box::new(affected_indices)) - // } else { - // (Box::new(affected_indices.chain((0..start_index).rev()) - // }; - - // Slash the active balance - slash_out_of(&mut self.active, &mut self.total, &mut remaining_slash); - - let mut slashed_unlocking = BTreeMap::<_, _>::new(); - // Slash the chunks we know are in the affected range for i in indices_to_slash { if let Some(chunk) = self.unlocking.get_mut(i) { - slash_out_of(&mut chunk.value, &mut self.total, &mut remaining_slash); + slash_out_of(&mut chunk.value, &mut remaining_slash); slashed_unlocking.insert(chunk.era, chunk.value); if remaining_slash.is_zero() { - break; + break } } else { - break; + break // defensive } } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 8b0473c0601c2..0ae9c2c860bae 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -275,9 +275,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 - + part_for_100_from_10 * total_payout_0 * 2 / 3 - + part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * total_payout_0 * 2 / 3 + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -313,9 +313,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 - + part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) - + part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -3967,8 +3967,8 @@ mod election_data_provider { #[test] fn targets_2sec_block() { let mut validators = 1000; - while ::WeightInfo::get_npos_targets(validators) - < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_targets(validators) < + 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { validators += 1; } @@ -3985,8 +3985,8 @@ mod election_data_provider { let slashing_spans = validators; let mut nominators = 1000; - while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) - < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) < + 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { nominators += 1; } @@ -4057,8 +4057,8 @@ mod election_data_provider { .build_and_execute(|| { // sum of all nominators who'd be voters (1), plus the self-votes (4). assert_eq!( - ::SortedListProvider::count() - + >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 5 ); @@ -4097,8 +4097,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() - + >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 7 ); @@ -4142,8 +4142,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() - + >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 6 ); From d39e45904dde13dec439b23630f8cb4b133a33e1 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 24 Feb 2022 22:57:39 -0800 Subject: [PATCH 139/299] Fix slash saturation --- frame/staking/src/lib.rs | 32 ++++++++++++++++---------------- frame/staking/src/slashing.rs | 5 ++++- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 89110cb3d260d..f102aac861701 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -315,11 +315,7 @@ use sp_staking::{ offence::{Offence, OffenceError, ReportOffence}, EraIndex, SessionIndex, }; -use sp_std::{ - collections::{btree_map::BTreeMap, btree_set::BTreeSet}, - convert::From, - prelude::*, -}; +use sp_std::{collections::btree_map::BTreeMap, convert::From, prelude::*}; pub use weights::WeightInfo; pub use pallet::{pallet::*, *}; @@ -545,8 +541,9 @@ impl StakingLedger { slash_era: EraIndex, _active_era: EraIndex, // TODO ) -> BalanceOf { + use sp_runtime::traits::CheckedMul as _; use sp_staking::OnStakerSlash as _; - use sp_std::ops::Div; + use sp_std::ops::Div as _; let mut remaining_slash = slash_amount; let pre_slash_total = self.total; @@ -576,20 +573,19 @@ impl StakingLedger { } // Wether or not this slash can be applied to just the active and affected unbonding chunks. // If not, we have to slash all of the aforementioned and then continue slashing older - // unlocking chunks + // unlocking chunks. let is_proportional_slash = affected_balance <= slash_amount; // Helper to update `target` and the ledgers total after accounting for slashing `target`. let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { + // We don't want the added complexity of using extended ints, so if this saturates we + // will just try and slash as much as possible. + let maybe_numerator = slash_amount.checked_mul(target); // Calculate the amount to slash from the target - let slash_from_target = if is_proportional_slash { + let slash_from_target = match (maybe_numerator, is_proportional_slash) { // Equivalent to `(slash_amount / affected_balance) * target`. - slash_amount - .saturating_mul(*target) - // Checked for zero above - .div(affected_balance) - } else { - (*slash_remaining).min(*target) + (Some(numerator), true) => numerator.div(affected_balance), + _ => (*slash_remaining).min(*target), }; *target = target.saturating_sub(slash_from_target); @@ -612,8 +608,12 @@ impl StakingLedger { let indices_to_slash = if is_proportional_slash { Box::new(affected_indices) as Box> } else { - Box::new(affected_indices.chain((0..start_index).rev())) - as Box> + Box::new( + // First slash unbonding chunks from after the slash + affected_indices + // Then start slashing older chunks, start from the era before the slash + .chain((0..start_index).rev()), + ) as Box> }; for i in indices_to_slash { if let Some(chunk) = self.unlocking.get_mut(i) { diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index ce1ba92e03ab8..7a04b31d332ad 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -611,8 +611,10 @@ pub fn do_slash( None => return, // nothing to do. }; + println!("{:?}=ledger.active 1", ledger.active); + println!("{:?}=value", value); let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era, apply_era); - + println!("{:?}=ledger.active 2", ledger.active); if !value.is_zero() { // TODO: if this happens the UnbondPools that did not get slashed could think // there are more funds unbonding then there really is, which could lead to attempts @@ -620,6 +622,7 @@ pub fn do_slash( // to solve this, when the go to withdraw unbonded, we can min the withdraw amount // with the non locked balance. let (imbalance, missing) = T::Currency::slash(stash, value); + println!("{:?}=missing", missing); slashed_imbalance.subsume(imbalance); if !missing.is_zero() { From 85d6c437dd336b8e5faceaddfb71a01707bb9131 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 24 Feb 2022 23:02:41 -0800 Subject: [PATCH 140/299] Remove unused param from slash --- frame/staking/src/lib.rs | 1 - frame/staking/src/slashing.rs | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index f102aac861701..32c624bfede69 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -539,7 +539,6 @@ impl StakingLedger { slash_amount: BalanceOf, minimum_balance: BalanceOf, slash_era: EraIndex, - _active_era: EraIndex, // TODO ) -> BalanceOf { use sp_runtime::traits::CheckedMul as _; use sp_staking::OnStakerSlash as _; diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 7a04b31d332ad..5df1a66d6c5d0 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -599,7 +599,6 @@ pub fn do_slash( reward_payout: &mut BalanceOf, slashed_imbalance: &mut NegativeImbalanceOf, slash_era: EraIndex, - apply_era: EraIndex, ) { let controller = match >::bonded(stash) { None => return, // defensive: should always exist. @@ -611,10 +610,7 @@ pub fn do_slash( None => return, // nothing to do. }; - println!("{:?}=ledger.active 1", ledger.active); - println!("{:?}=value", value); - let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era, apply_era); - println!("{:?}=ledger.active 2", ledger.active); + let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era); if !value.is_zero() { // TODO: if this happens the UnbondPools that did not get slashed could think // there are more funds unbonding then there really is, which could lead to attempts @@ -622,7 +618,6 @@ pub fn do_slash( // to solve this, when the go to withdraw unbonded, we can min the withdraw amount // with the non locked balance. let (imbalance, missing) = T::Currency::slash(stash, value); - println!("{:?}=missing", missing); slashed_imbalance.subsume(imbalance); if !missing.is_zero() { @@ -652,7 +647,6 @@ pub(crate) fn apply_slash( &mut reward_payout, &mut slashed_imbalance, slash_era, - active_era, ); for &(ref nominator, nominator_slash) in &unapplied_slash.others { @@ -662,7 +656,6 @@ pub(crate) fn apply_slash( &mut reward_payout, &mut slashed_imbalance, slash_era, - active_era, ); } From 727be7ba3626c18fb90b53a85e4aa89da482e1b0 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 24 Feb 2022 23:14:53 -0800 Subject: [PATCH 141/299] Docs and warnings --- frame/nomination-pools/src/lib.rs | 38 ++++++++++++++----------------- frame/staking/src/lib.rs | 18 ++++++++------- frame/staking/src/pallet/impls.rs | 25 ++++++++++---------- frame/staking/src/slashing.rs | 1 - 4 files changed, 40 insertions(+), 42 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 831cc58842971..65b36cc1ebd73 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -253,14 +253,9 @@ //! //! ### Slashing //! -//! Slashes are computed and executed by: -//! -//! 1) Balances of the bonded pool and the unbonding pools in range `slash_era + -//! 1..=apply_era` are summed and stored in `total_balance_affected`. -//! 2) `slash_ratio` is computed as `slash_amount / total_balance_affected`. -//! 3) `bonded_pool_balance_after_slash`is computed as `(1- slash_ratio) * bonded_pool_balance`. -//! 4) For all `unbonding_pool` in range `slash_era + 1..=apply_era` set their balance to `(1 - -//! slash_ratio) * unbonding_pool_balance`. +//! It is assumed that the slash computation is executed by +//! [`pallet_staking::StakingLedger::slash`], which passes the information to this pallet via +//! [`sp_staking::OnStakerSlash::on_slash`]. //! //! Unbonding pools need to be slashed to ensure all nominators whom where in the bonded pool //! while it was backing a validator that equivocated are punished. Without these measures a @@ -346,8 +341,9 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), + (true, true) | (false, true) => { + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) + }, (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -372,7 +368,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero() + return Zero::zero(); } // Equivalent of (current_balance / current_points) * delegator_points @@ -524,8 +520,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) < - BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) + < BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -701,7 +697,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self + return self; } // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -1116,8 +1112,8 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); ensure!( - current_era.saturating_sub(unbonding_era) >= - T::StakingInterface::bonding_duration(), + current_era.saturating_sub(unbonding_era) + >= T::StakingInterface::bonding_duration(), Error::::NotUnbondedYet ); @@ -1219,8 +1215,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() && - amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() + && amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1375,9 +1371,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() || - current_points.is_zero() || - reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() + || current_points.is_zero() + || reward_pool.balance.is_zero() { Zero::zero() } else { diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 32c624bfede69..c5ba4f8f4de5d 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -526,14 +526,16 @@ impl StakingLedger { /// /// Note that this calls `Config::OnStakerSlash::on_slash` with information as to how the slash /// was applied. - // - // Slashes are computed and executed by: - // - // 1) Balances of the unlocking chunks in range `slash_era + 1..=apply_era` are summed and - // stored in `total_balance_affected`. 2) `slash_ratio` is computed as `slash_amount / - // total_balance_affected`. 3) `Ledger::active` is set to `(1- slash_ratio) * Ledger::active`. - // 4) For all unlocking chunks in range `slash_era + 1..=apply_era` set their balance to `(1 - - // slash_ratio) * unbonding_pool_balance`. + /// + /// Slashes are computed by: + /// + /// 1) Balances of the unlocking chunks in range `slash_era + 1..=apply_era` are summed and + /// stored in `total_balance_affected`. 2) `slash_ratio` is computed as `slash_amount / + /// total_balance_affected`. 3) `Ledger::active` is set to `(1- slash_ratio) * Ledger::active`. + /// 4) For all unlocking chunks in range `slash_era + 1..=apply_era` set their balance to `(1 - + /// slash_ratio) * unbonding_pool_balance`. + /// 5) Slash any remaining slash amount from the remaining chunks, starting with the `slash_era` + /// and going backwards. fn slash( &mut self, slash_amount: BalanceOf, diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index fb466f1d91c0b..c1abb27c75c61 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -143,7 +143,7 @@ impl Pallet { // Nothing to do if they have no reward points. if validator_reward_points.is_zero() { - return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into()) + return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into()); } // This is the fraction of the total reward that the validator and the @@ -232,8 +232,9 @@ impl Pallet { Self::update_ledger(&controller, &l); r }), - RewardDestination::Account(dest_account) => - Some(T::Currency::deposit_creating(&dest_account, amount)), + RewardDestination::Account(dest_account) => { + Some(T::Currency::deposit_creating(&dest_account, amount)) + }, RewardDestination::None => None, } } @@ -261,14 +262,14 @@ impl Pallet { _ => { // Either `Forcing::ForceNone`, // or `Forcing::NotForcing if era_length >= T::SessionsPerEra::get()`. - return None + return None; }, } // New era. let maybe_new_era_validators = Self::try_trigger_new_era(session_index, is_genesis); - if maybe_new_era_validators.is_some() && - matches!(ForceEra::::get(), Forcing::ForceNew) + if maybe_new_era_validators.is_some() + && matches!(ForceEra::::get(), Forcing::ForceNew) { ForceEra::::put(Forcing::NotForcing); } @@ -459,7 +460,7 @@ impl Pallet { } Self::deposit_event(Event::StakingElectionFailed); - return None + return None; } Self::deposit_event(Event::StakersElected); @@ -591,7 +592,7 @@ impl Pallet { for era in (*earliest)..keep_from { let era_slashes = ::UnappliedSlashes::take(&era); for slash in era_slashes { - slashing::apply_slash::(slash, era, active_era); + slashing::apply_slash::(slash, era); } } @@ -870,7 +871,7 @@ impl ElectionDataProvider for Pallet { // We can't handle this case yet -- return an error. if maybe_max_len.map_or(false, |max_len| target_count > max_len as u32) { - return Err("Target snapshot too big") + return Err("Target snapshot too big"); } Ok(Self::get_npos_targets()) @@ -1144,7 +1145,7 @@ where add_db_reads_writes(1, 0); if active_era.is_none() { // This offence need not be re-submitted. - return consumed_weight + return consumed_weight; } active_era.expect("value checked not to be `None`; qed").index }; @@ -1190,7 +1191,7 @@ where // Skip if the validator is invulnerable. if invulnerables.contains(stash) { - continue + continue; } let unapplied = slashing::compute_slash::(slashing::SlashParams { @@ -1216,7 +1217,7 @@ where unapplied.reporters = details.reporters.clone(); if slash_defer_duration == 0 { // Apply right away. - slashing::apply_slash::(unapplied, slash_era, active_era); + slashing::apply_slash::(unapplied, slash_era); { let slash_cost = (6, 5); let reward_cost = (2, 2); diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 5df1a66d6c5d0..773d7ca9ddb16 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -636,7 +636,6 @@ pub fn do_slash( pub(crate) fn apply_slash( unapplied_slash: UnappliedSlash>, slash_era: EraIndex, - active_era: EraIndex, ) { let mut slashed_imbalance = NegativeImbalanceOf::::zero(); let mut reward_payout = unapplied_slash.payout; From a993390517237fb176a86d4dcd1cbaa290611865 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 25 Feb 2022 20:00:35 -0800 Subject: [PATCH 142/299] Test ledger::slash --- frame/nomination-pools/src/lib.rs | 87 +++--- frame/staking/src/lib.rs | 27 +- frame/staking/src/pallet/impls.rs | 21 +- frame/staking/src/tests.rs | 424 +++++++++++++++--------------- 4 files changed, 284 insertions(+), 275 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 65b36cc1ebd73..df919bc2d6419 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -341,9 +341,8 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => { - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) - }, + (true, true) | (false, true) => + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -368,7 +367,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero(); + return Zero::zero() } // Equivalent of (current_balance / current_points) * delegator_points @@ -520,8 +519,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) - < BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) < + BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -642,7 +641,7 @@ pub struct RewardPool { impl RewardPool { /// Mutate the reward pool by updating the total earnings and current free balance. fn update_total_earnings_and_balance(&mut self) { - let current_balance = T::Currency::free_balance(&self.account); + let current_balance = T::Currency::usable_balance(&self.account); // The earnings since the last time it was updated let new_earnings = current_balance.saturating_sub(self.balance); // The lifetime earnings of the of the reward pool @@ -697,7 +696,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self; + return self } // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -1112,8 +1111,8 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); ensure!( - current_era.saturating_sub(unbonding_era) - >= T::StakingInterface::bonding_duration(), + current_era.saturating_sub(unbonding_era) >= + T::StakingInterface::bonding_duration(), Error::::NotUnbondedYet ); @@ -1124,31 +1123,35 @@ pub mod pallet { let should_remove_pool = bonded_pool .ok_to_withdraw_unbonded_other_with(&caller, &target, &delegator, &sub_pools)?; - let balance_to_unbond = if let Some(pool) = sub_pools.with_era.get_mut(&unbonding_era) { - let balance_to_unbond = pool.balance_to_unbond(delegator.points); - pool.points = pool.points.saturating_sub(delegator.points); - pool.balance = pool.balance.saturating_sub(balance_to_unbond); - if pool.points.is_zero() { - // Clean up pool that is no longer used - sub_pools.with_era.remove(&unbonding_era); + let balance_to_unbond = + if let Some(pool) = sub_pools.with_era.get_mut(&unbonding_era) { + let balance_to_unbond = pool.balance_to_unbond(delegator.points); + pool.points = pool.points.saturating_sub(delegator.points); + pool.balance = pool.balance.saturating_sub(balance_to_unbond); + if pool.points.is_zero() { + // Clean up pool that is no longer used + sub_pools.with_era.remove(&unbonding_era); + } + + balance_to_unbond + } else { + // A pool does not belong to this era, so it must have been merged to the + // era-less pool. + let balance_to_unbond = sub_pools.no_era.balance_to_unbond(delegator.points); + sub_pools.no_era.points = + sub_pools.no_era.points.saturating_sub(delegator.points); + sub_pools.no_era.balance = + sub_pools.no_era.balance.saturating_sub(balance_to_unbond); + + balance_to_unbond } - - balance_to_unbond - } else { - // A pool does not belong to this era, so it must have been merged to the era-less - // pool. - let balance_to_unbond = sub_pools.no_era.balance_to_unbond(delegator.points); - sub_pools.no_era.points = sub_pools.no_era.points.saturating_sub(delegator.points); - sub_pools.no_era.balance = - sub_pools.no_era.balance.saturating_sub(balance_to_unbond); - - balance_to_unbond - }; + // In the rare case a pool had such an extreme slash that it erased all the bonded + // balance and balance in unlocking chunks + .min(T::Currency::usable_balance(&delegator.pool)); T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; - // TODO Make sure T::Currency::free_balance(&delegator.pool) doesn't include bonded - // else this check is bad - if T::Currency::free_balance(&delegator.pool) >= balance_to_unbond { + + if balance_to_unbond <= T::Currency::minimum_balance() { T::Currency::transfer( &delegator.pool, &target, @@ -1162,9 +1165,13 @@ pub mod pallet { amount: balance_to_unbond, }); } else { - // This should only happen in the case a previous withdraw put the pools balance - // below ED and it was dusted. We gracefully carry primarily to ensure the pool can - // eventually be destroyed + // This should only happen if 1) a previous withdraw put the pools balance + // below ED and it was dusted or 2) the pool was slashed a huge amount that wiped + // all the unlocking chunks and bonded balance, thus causing inconsistencies with + // unbond pool's tracked balance and the actual balance (if this happens, the pool + // is in an invalid state anyways because there are no bonded funds so no one can + // join). We gracefully carry on, primarily to ensure the pool can eventually be + // destroyed Self::deposit_event(Event::::DustWithdrawn { delegator: target.clone(), pool: delegator.pool.clone(), @@ -1215,8 +1222,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() - && amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() && + amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1371,9 +1378,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() - || current_points.is_zero() - || reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() || + current_points.is_zero() || + reward_pool.balance.is_zero() { Zero::zero() } else { diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index c5ba4f8f4de5d..7587d97d80df2 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -549,7 +549,7 @@ impl StakingLedger { let mut remaining_slash = slash_amount; let pre_slash_total = self.total; - // The index of the first chunk after the slash + // The index of the first chunk after the slash (OR the last index if the let start_index = self.unlocking.partition_point(|c| c.era < slash_era); // The indices of from the first chunk after the slash up through the most recent chunk. // (The most recent chunk is at greatest from this era) @@ -575,25 +575,27 @@ impl StakingLedger { // Wether or not this slash can be applied to just the active and affected unbonding chunks. // If not, we have to slash all of the aforementioned and then continue slashing older // unlocking chunks. - let is_proportional_slash = affected_balance <= slash_amount; + let is_proportional_slash = slash_amount < affected_balance; // Helper to update `target` and the ledgers total after accounting for slashing `target`. let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { // We don't want the added complexity of using extended ints, so if this saturates we - // will just try and slash as much as possible. + // will always just try and slash as much as possible. let maybe_numerator = slash_amount.checked_mul(target); + println!("{:?}=maybe_numerator", maybe_numerator); + // Calculate the amount to slash from the target let slash_from_target = match (maybe_numerator, is_proportional_slash) { // Equivalent to `(slash_amount / affected_balance) * target`. (Some(numerator), true) => numerator.div(affected_balance), - _ => (*slash_remaining).min(*target), + (None, _) | (_, false) => (*slash_remaining).min(*target), }; *target = target.saturating_sub(slash_from_target); let actual_slashed = if *target <= minimum_balance { // Slash the rest of the target if its dust - sp_std::mem::replace(target, Zero::zero()) + slash_from_target + sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) } else { slash_from_target }; @@ -606,16 +608,11 @@ impl StakingLedger { slash_out_of(&mut self.active, &mut remaining_slash); let mut slashed_unlocking = BTreeMap::<_, _>::new(); - let indices_to_slash = if is_proportional_slash { - Box::new(affected_indices) as Box> - } else { - Box::new( - // First slash unbonding chunks from after the slash - affected_indices + let indices_to_slash + // First slash unbonding chunks from after the slash + = affected_indices // Then start slashing older chunks, start from the era before the slash - .chain((0..start_index).rev()), - ) as Box> - }; + .chain((0..start_index).rev()); for i in indices_to_slash { if let Some(chunk) = self.unlocking.get_mut(i) { slash_out_of(&mut chunk.value, &mut remaining_slash); @@ -625,7 +622,7 @@ impl StakingLedger { break } } else { - break // defensive + break // defensive, indices should always be in bounds. } } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index c1abb27c75c61..109a59362cb68 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -143,7 +143,7 @@ impl Pallet { // Nothing to do if they have no reward points. if validator_reward_points.is_zero() { - return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into()); + return Ok(Some(T::WeightInfo::payout_stakers_alive_staked(0)).into()) } // This is the fraction of the total reward that the validator and the @@ -232,9 +232,8 @@ impl Pallet { Self::update_ledger(&controller, &l); r }), - RewardDestination::Account(dest_account) => { - Some(T::Currency::deposit_creating(&dest_account, amount)) - }, + RewardDestination::Account(dest_account) => + Some(T::Currency::deposit_creating(&dest_account, amount)), RewardDestination::None => None, } } @@ -262,14 +261,14 @@ impl Pallet { _ => { // Either `Forcing::ForceNone`, // or `Forcing::NotForcing if era_length >= T::SessionsPerEra::get()`. - return None; + return None }, } // New era. let maybe_new_era_validators = Self::try_trigger_new_era(session_index, is_genesis); - if maybe_new_era_validators.is_some() - && matches!(ForceEra::::get(), Forcing::ForceNew) + if maybe_new_era_validators.is_some() && + matches!(ForceEra::::get(), Forcing::ForceNew) { ForceEra::::put(Forcing::NotForcing); } @@ -460,7 +459,7 @@ impl Pallet { } Self::deposit_event(Event::StakingElectionFailed); - return None; + return None } Self::deposit_event(Event::StakersElected); @@ -871,7 +870,7 @@ impl ElectionDataProvider for Pallet { // We can't handle this case yet -- return an error. if maybe_max_len.map_or(false, |max_len| target_count > max_len as u32) { - return Err("Target snapshot too big"); + return Err("Target snapshot too big") } Ok(Self::get_npos_targets()) @@ -1145,7 +1144,7 @@ where add_db_reads_writes(1, 0); if active_era.is_none() { // This offence need not be re-submitted. - return consumed_weight; + return consumed_weight } active_era.expect("value checked not to be `None`; qed").index }; @@ -1191,7 +1190,7 @@ where // Skip if the validator is invulnerable. if invulnerables.contains(stash) { - continue; + continue } let unapplied = slashing::compute_slash::(slashing::SlashParams { diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 0ae9c2c860bae..3eb8c307bff9e 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4750,213 +4750,219 @@ fn force_apply_min_commission_works() { // TODO: adapt these for the staking use case #[test] -fn ledger_slash_works_in_simple_cases() { - // Given no unlocking chunks - // let ledger = StakingLedger:: { - // stash: 123, - // total: 15, - // active: 10, - // unlocking: vec![], - // claimed_rewards: vec![], - // }; - - // // When - // assert_eq!(ledger.slash(5, 1, 0, 10), 0); - - // // Then - // assert_eq!(ledger.total, 10); - // assert_eq!(ledger.active, 5); - - // // When the slash amount is greater then the total - // assert_eq!(ledger.slash(11, 1, 0, 10), 1); - - // // Then - // assert_eq!(ledger.total, 0); - // assert_eq!(ledger.active, 0); - - // // Given - // let chunks = [0, 1, 8, 9].iter().map(|era| UnlockChunks { era, value: 10 }); - // ledger.total = 4 * 10; - // ledger.chunks = chunks.clone(); - - // // When no chunks overlap with the slash eras, - // assert_eq!(ledger.slash(10, 1, 1, 7), 10); - - // // Then - // assert_eq(ledger.total, 4 * 10); - - // // Slash, but all sub pools are out of range - // ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { - // // Given - // // Unbond in era 0 - // assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); - - // // When - // let SlashPoolOut { slashed_bonded, slashed_unlocking } = - // Pools::slash_pool(SlashPoolArgs { - // pool_stash: &PRIMARY_ACCOUNT, - // slash_amount: 9, - // // We start slashing unbonding pools in `slash_era + 1` - // slash_era: 0, - // apply_era: 3, - // active_bonded: 100, - // }) - // .unwrap(); - - // // Then - // assert_eq!( - // SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), - // SubPools { - // no_era: Default::default(), - // with_era: sub_pools_with_era! { - // 0 => UnbondPool { points: 100, balance: 100 } - // } - // } - // ); - // assert_eq!(slashed_unlocking, Default::default()); - // assert_eq!(slashed_bonded, 91); - // }); +fn ledger_slash_works() { + let c = |era, value| UnlockChunk:: { era, value }; + // Given + let mut ledger = StakingLedger:: { + stash: 123, + total: 10, + active: 10, + unlocking: vec![], + claimed_rewards: vec![], + }; + + // When we slash a ledger with no unlocking chunks + assert_eq!(ledger.slash(5, 1, 0), 5); + // Then + assert_eq!(ledger.total, 5); + assert_eq!(ledger.active, 5); + + // When we slash a ledger with no unlocking chunks and the slash amount is greater then the + // total + assert_eq!(ledger.slash(11, 1, 0), 5); + // Then + assert_eq!(ledger.total, 0); + assert_eq!(ledger.active, 0); + + // Given + ledger.unlocking = vec![c(0, 10), c(1, 10)]; + ledger.total = 2 * 10; + ledger.active = 0; + // When all the chunks overlap with the slash eras + assert_eq!(ledger.slash(20, 0, 0), 20); + // Then + assert_eq!(ledger.total, 0); + assert_eq!(ledger.unlocking, vec![c(0, 0), c(1, 0)]); + + // Given + ledger.unlocking = vec![c(0, 40), c(1, 100), c(2, 10), c(3, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + + // When we have a partial slash that touches all chunks + assert_eq!(ledger.slash(900 / 2, 0, 0), 450); + + // Then + assert_eq!(ledger.unlocking, vec![c(0, 40 / 2), c(1, 100 / 2), c(2, 10 / 2), c(3, 250 / 2)]); + assert_eq!(ledger.active, 500 / 2); + assert_eq!(ledger.total, 900 / 2); + + // Given we have the same as above, + ledger.unlocking = vec![c(0, 40), c(1, 100), c(2, 10), c(3, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + assert_eq!(ledger.total, 900); + + // When we have a higher min balance + assert_eq!( + ledger.slash( + 900 / 2, + 25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it to + * get swept */ + 0 + ), + 475 + ); + + // Then + assert_eq!(ledger.active, 500 / 2); + assert_eq!(ledger.unlocking, vec![c(0, 0), c(1, 100 / 2), c(2, 0), c(3, 250 / 2)]); + assert_eq!(ledger.total, 425); + + // Given we have the same as above, + ledger.unlocking = vec![c(0, 40), c(1, 100), c(2, 10), c(3, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + + // When + assert_eq!( + ledger.slash( + 900 / 2, + 25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it to + * get swept */ + 0 + ), + 475 + ); + + // Then + assert_eq!(ledger.active, 500 / 2); + assert_eq!(ledger.unlocking, vec![c(0, 0), c(1, 100 / 2), c(2, 0), c(3, 250 / 2)]); + assert_eq!(ledger.total, 425); + + // Given + ledger.unlocking = vec![c(0, 40), c(1, 100), c(2, 10), c(3, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + + // When + assert_eq!( + ledger.slash( + 500 + 10 + 250 + 100 / 2, // active + era 2 + era 3 + era 1 / 2 + 0, + 2 /* slash era 2 first, so the affected parts are era 2, era 3 and ledge.active. + * This will cause the affected to go to zero, and then we will start slashing + * older chunks */ + ), + 500 + 250 + 10 + 100 / 2 + ); + + assert_eq!(ledger.active, 0); + // iteration order ------------------NA----------2-------------0--------1---- + assert_eq!(ledger.unlocking, vec![c(0, 40), c(1, 100 / 2), c(2, 0), c(3, 0)]); + assert_eq!(ledger.total, 90); + + // Given + ledger.unlocking = vec![c(0, 100), c(1, 100), c(2, 100), c(3, 100)]; + ledger.active = 100; + ledger.total = 5 * 100; + + // When + assert_eq!( + ledger.slash( + 350, // active + era 2 + era 3 + era 1 / 2 + 50, + 2 /* slash era 2 first, so the affected parts are era 2, era 3 and ledge.active. + * This will cause the affected to go to zero, and then we will start slashing + * older chunks */ + ), + 400 + ); + + // Then + assert_eq!(ledger.active, 0); + // iteration order ------------------NA---------2--------0--------1---- + assert_eq!(ledger.unlocking, vec![c(0, 100), c(1, 0), c(2, 0), c(3, 0)]); + //------goes to min balance and then gets dusted^^^ + assert_eq!(ledger.total, 100); + + // Tests for saturating arithmetic + + // Given + let slash = u64::MAX as Balance * 2; + let value = slash + - (9 * 4) // The value of the other parts of ledger that will get slashed + + 1; + // slash * value will saturate + assert!(slash.checked_mul(value - 20).is_none()); + + ledger.active = 10; + ledger.unlocking = vec![c(0, 10), c(1, 10), c(2, 10), c(3, value)]; + + ledger.total = value + 40; + + // When + assert_eq!(ledger.slash(slash, 0, 0), slash); + + // Then + assert_eq!(ledger.active, 1); // slash of 9 + assert_eq!( + ledger.unlocking, + vec![ + c(0, 1), // slash of 9 + c(1, 1), // slash of 9 + c(2, 1), // slash of 9 ------------(slash - 9 * 4).min(value) + c(3, 1), // saturates, so slash of remaining_slash.min(value) + ] + ); + assert_eq!(ledger.total, 5); + + // Given + let slash = u64::MAX as Balance * 2; + let value = u64::MAX as Balance * 2; + let unit = 100; + // slash * value that will saturate + assert!(slash.checked_mul(value).is_none()); + // but slash * unit. + assert!(slash.checked_mul(unit).is_some()); + + ledger.unlocking = vec![c(0, unit), c(1, value), c(2, unit), c(3, unit)]; + //------------------------------note value^^^ + ledger.active = unit; + ledger.total = unit * 4 + value; + + // When + assert_eq!(ledger.slash(slash, 0, 0), slash); + + // Then + + // The amount slashed out of `unit` + let unit_slash = { + let affected_balance = value + unit * 4; + slash * unit / affected_balance + }; + // `unit` after the slash is applied + let unit_slashed = unit - unit_slash; + + // `value` after the slash is applied + let value_slashed = { + // We slash active and era 1 before we slash era 2 + let previous_slashed_amount = unit_slash * 2; + let remaining_slash = slash - previous_slashed_amount; + value - remaining_slash + }; + assert_eq!(ledger.active, unit_slashed); + assert_eq!( + ledger.unlocking, + vec![ + c(0, unit_slashed), + // We reached the slash here because we slashed value.min(remaining_slash), which was + // remaining_slash + c(1, value_slashed), + c(2, unit), /* The rest are untouched even though they where in the slashing range. + * This is problematic for pools, but should be rare enough that its ok. */ + c(3, unit) + ] + ); + assert_eq!(ledger.total, unit_slashed + unit_slashed + value_slashed + unit + unit); } - -// // Some sub pools are in range of the slash while others are not. -// #[test] -// fn slash_pool_works_in_complex_cases() { -// ExtBuilder::default() -// .add_delegators(vec![(40, 40), (100, 100), (200, 200), (300, 300), (400, 400)]) -// .build_and_execute(|| { -// // Make sure no pools get merged into `SubPools::no_era` until era 7. -// BondingDuration::set(5); -// assert_eq!(TotalUnbondingPools::::get(), 7); - -// assert_ok!(Pools::unbond_other(Origin::signed(400), 400)); - -// CurrentEra::set(1); -// assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); - -// CurrentEra::set(3); -// assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); - -// CurrentEra::set(5); -// assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); - -// CurrentEra::set(6); -// assert_ok!(Pools::unbond_other(Origin::signed(300), 300)); - -// // Given -// assert_eq!( -// SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), -// SubPools { -// no_era: Default::default(), -// with_era: sub_pools_with_era! { -// 0 => UnbondPool { points: 400, balance: 400 }, -// 1 => UnbondPool { points: 40, balance: 40 }, -// 3 => UnbondPool { points: 100, balance: 100 }, -// 5 => UnbondPool { points: 200, balance: 200 }, -// 6 => UnbondPool { points: 300, balance: 300 }, -// } -// } -// ); - -// // When -// let SlashPoolOut { slashed_bonded, slashed_unlocking } = -// Pools::slash_pool(SlashPoolArgs { -// pool_stash: &PRIMARY_ACCOUNT, -// slash_amount: (40 + 100 + 200 + 10) / 2, -// slash_era: 0, -// apply_era: 5, -// active_bonded: 10, -// }) -// .unwrap(); - -// // Then -// assert_eq!( -// SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), -// SubPools { -// no_era: Default::default(), -// with_era: sub_pools_with_era! { -// 0 => UnbondPool { points: 400, balance: 400 }, -// 1 => UnbondPool { points: 40, balance: 40 / 2 }, -// 3 => UnbondPool { points: 100, balance: 100 / 2}, -// 5 => UnbondPool { points: 200, balance: 200 / 2}, -// 6 => UnbondPool { points: 300, balance: 300 }, -// } -// } -// ); -// let expected_slashed_unlocking: BTreeMap<_, _> = -// [(1, 40 / 2), (3, 100 / 2), (5, 200 / 2)].into_iter().collect(); -// assert_eq!(slashed_unlocking, expected_slashed_unlocking); -// assert_eq!(slashed_bonded, 10 / 2); -// }); -// } - -// // Same as above, but the slash amount is greater than the slash-able balance of the pool. -// #[test] -// fn pool_slash_works_with_slash_amount_greater_than_slashable() { -// ExtBuilder::default() -// .add_delegators(vec![(40, 40), (100, 100), (200, 200), (300, 300), (400, 400)]) -// .build_and_execute(|| { -// // Make sure no pools get merged into `SubPools::no_era` until era 7. -// BondingDuration::set(5); -// assert_eq!(TotalUnbondingPools::::get(), 7); - -// assert_ok!(Pools::unbond_other(Origin::signed(400), 400)); - -// CurrentEra::set(1); -// assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); - -// CurrentEra::set(3); -// assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); - -// CurrentEra::set(5); -// assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); - -// CurrentEra::set(6); -// assert_ok!(Pools::unbond_other(Origin::signed(300), 300)); - -// // Given -// assert_eq!( -// SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), -// SubPools { -// no_era: Default::default(), -// with_era: sub_pools_with_era! { -// 0 => UnbondPool { points: 400, balance: 400 }, -// 1 => UnbondPool { points: 40, balance: 40 }, -// 3 => UnbondPool { points: 100, balance: 100 }, -// 5 => UnbondPool { points: 200, balance: 200 }, -// 6 => UnbondPool { points: 300, balance: 300 }, -// } -// } -// ); - -// // When -// let SlashPoolOut { slashed_bonded, slashed_unlocking } = -// Pools::slash_pool(SlashPoolArgs { -// pool_stash: &PRIMARY_ACCOUNT, -// slash_amount: 40 + 100 + 200 + 400 + 10, -// slash_era: 0, -// apply_era: 5, -// active_bonded: 10, -// }) -// .unwrap(); - -// // Then -// assert_eq!( -// SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), -// SubPools { -// no_era: Default::default(), -// with_era: sub_pools_with_era! { -// 0 => UnbondPool { points: 400, balance: 400 }, -// 1 => UnbondPool { points: 40, balance: 0 }, -// 3 => UnbondPool { points: 100, balance: 0 }, -// 5 => UnbondPool { points: 200, balance: 0 }, -// 6 => UnbondPool { points: 300, balance: 300 }, -// } -// } -// ); -// let expected_slashed_unlocking: BTreeMap<_, _> = -// [(1, 0), (3, 0), (5, 0)].into_iter().collect(); -// assert_eq!(slashed_unlocking, expected_slashed_unlocking); -// assert_eq!(slashed_bonded, 0); -// }); -// } -// } From 1a30c10c0903380120586727a54c470dde45f2c6 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 28 Feb 2022 16:55:56 -0800 Subject: [PATCH 143/299] save progress --- frame/nomination-pools/src/lib.rs | 208 +++++++++++++++++++---------- frame/nomination-pools/src/mock.rs | 4 + frame/staking/src/lib.rs | 17 +-- frame/staking/src/mock.rs | 12 ++ frame/staking/src/tests.rs | 2 - primitives/staking/src/lib.rs | 65 +-------- 6 files changed, 164 insertions(+), 144 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index df919bc2d6419..52516e7102705 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -41,7 +41,7 @@ //! First, they must call [`Call::unbond_other`]. The unbond other extrinsic will start the //! unbonding process by unbonding all of the delegators funds. //! -//! Second, Once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the delegator +//! Second, once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the delegator //! can call [`Call::withdraw_unbonded_other`] to withdraw all there funds. //! //! For design docs see the [bonded pool](#bonded-pool) and [unbonding sub @@ -253,7 +253,7 @@ //! //! ### Slashing //! -//! It is assumed that the slash computation is executed by +//! This section assumes that the slash computation is executed by //! [`pallet_staking::StakingLedger::slash`], which passes the information to this pallet via //! [`sp_staking::OnStakerSlash::on_slash`]. //! @@ -283,7 +283,7 @@ //! //! # Runtime builder warnings //! -//! * watch out for overflow of [`RewardPoints`] and [`BalanceOf`] types. Consider things like the +//! * Watch out for overflow of [`RewardPoints`] and [`BalanceOf`] types. Consider things like the //! chains total issuance, staking reward rate, and burn rate. // // Invariants @@ -293,11 +293,16 @@ // calculated amount when withdrawing unbonded is a lower bound of the pools free balance. // * If the depositor is actively unbonding, the pool is in destroying state. To achieve this, once // a pool is flipped to a destroying state it cannot change its state. +// * The sum of each pools delegator counter equals the `Delegators::count()`. // TODO // - Counter for delegators per pool and allow limiting of delegators per pool +// - tests for the above // - write detailed docs for StakingInterface // - various back ports +// - test delegator counter +// - slashing - test for correct input to hook +// - transparent account ids // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -341,8 +346,9 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), + (true, true) | (false, true) => { + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) + }, (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -367,7 +373,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero() + return Zero::zero(); } // Equivalent of (current_balance / current_points) * delegator_points @@ -388,7 +394,7 @@ pub struct Delegator { pub points: BalanceOf, /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. /// Assuming no massive burning events, we expect this value to always be below total issuance. - /// This value lines up with the `RewardPool::total_earnings` after a delegator claims a + /// This value lines up with the [`RewardPool::total_earnings`] after a delegator claims a /// payout. pub reward_pool_total_earnings: BalanceOf, /// The era this delegator started unbonding at. @@ -412,6 +418,10 @@ pub enum PoolState { pub struct BondedPoolStorage { /// See [`BondedPool::points`]. pub points: BalanceOf, + /// See [`BondedPool::state_toggler`]. + pub state: PoolState, + /// See [`BondedPool::delegator_counter`] + pub delegator_counter: u32, /// See [`BondedPool::depositor`]. pub depositor: T::AccountId, /// See [`BondedPool::admin`]. @@ -420,15 +430,19 @@ pub struct BondedPoolStorage { pub nominator: T::AccountId, /// See [`BondedPool::state_toggler`]. pub state_toggler: T::AccountId, - /// See [`BondedPool::state_toggler`]. - pub state: PoolState, } #[derive(RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] pub struct BondedPool { - /// Points of the pool. + /// Points of the pool. Each delegator has some corresponding the points. The portion of points + /// that belong to a delegator represent the portion of the pools bonded funds belong to the + /// delegator. points: BalanceOf, + /// State of the pool. + state: PoolState, + /// Count of delegators that belong to this pool. + delegator_counter: u32, /// Account that puts down a deposit to create the pool. This account acts a delegator, but can /// only unbond if no other delegators belong to the pool. depositor: T::AccountId, @@ -440,8 +454,6 @@ pub struct BondedPool { /// Can toggle the pools state, including setting the pool as blocked or putting the pool into /// destruction mode. The state toggle can also "kick" delegators by unbonding them. state_toggler: T::AccountId, - /// State of the pool. - state: PoolState, /// AccountId of the pool. account: T::AccountId, } @@ -449,25 +461,31 @@ pub struct BondedPool { impl BondedPool { /// Get [`Self`] from storage. Returns `None` if no entry for `pool_account` exists. fn get(pool_account: &T::AccountId) -> Option { - BondedPools::::try_get(pool_account).ok().map( - |BondedPoolStorage { points, depositor, root, nominator, state_toggler, state }| Self { - points, - depositor, - root, - nominator, - state_toggler, - state, - account: pool_account.clone(), - }, - ) + BondedPools::::try_get(pool_account).ok().map(|storage| Self { + points: storage.points, + delegator_counter: storage.delegator_counter, + state_toggler: storage.state_toggler, + depositor: storage.depositor, + root: storage.root, + nominator: storage.nominator, + state: storage.state, + account: pool_account.clone(), + }) } /// Consume and put [`Self`] into storage. fn put(self) { - let Self { account, points, depositor, root, nominator, state_toggler, state } = self; BondedPools::::insert( - account, - BondedPoolStorage { points, depositor, root, nominator, state_toggler, state }, + self.account, + BondedPoolStorage { + points: self.points, + delegator_counter: self.delegator_counter, + depositor: self.depositor, + root: self.root, + nominator: self.nominator, + state_toggler: self.state_toggler, + state: self.state, + }, ); } @@ -519,8 +537,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) < - BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) + < BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -533,6 +551,7 @@ impl BondedPool { } fn can_kick(&self, who: &T::AccountId) -> bool { + let (root, toggler, state) = (self.root, self.state_toggler, self.state); *who == self.root || *who == self.state_toggler && self.state == PoolState::Blocked } @@ -616,6 +635,31 @@ impl BondedPool { Ok(false) } } + + /// Increment the delegator counter. Ensures that the pool and system delegator limits are + /// respected. + fn inc_delegators(&self) -> Result<(), DispatchError> { + if let Some(max_per_pool) = MaxDelegatorsPerPool::::get() { + ensure!(self.delegator_counter < max_per_pool, Error::::MaxDelegators); + } + if let Some(max) = MaxDelegators::::get() { + ensure!(Delegators::::count() < max, Error::::MaxDelegators); + } + self.delegator_counter += 1; + Ok(()) + } + + /// Decrement the delegator counter. + fn dec_delegators(self) -> Self { + self.delegator_counter -= 1; + self + } + + fn non_bonded_balance(&self) -> BalanceOf { + T::Currency::free_balance(&self.account).saturating_sub( + T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()), + ) + } } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] @@ -641,7 +685,7 @@ pub struct RewardPool { impl RewardPool { /// Mutate the reward pool by updating the total earnings and current free balance. fn update_total_earnings_and_balance(&mut self) { - let current_balance = T::Currency::usable_balance(&self.account); + let current_balance = T::Currency::free_balance(&self.account); // The earnings since the last time it was updated let new_earnings = current_balance.saturating_sub(self.balance); // The lifetime earnings of the of the reward pool @@ -696,7 +740,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self + return self; } // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -804,6 +848,16 @@ pub mod pallet { #[pallet::storage] pub type MaxPools = StorageValue<_, u32, OptionQuery>; + /// Maximum number of delegators that can exist in the system. If `None`, then the count + /// delegators are not bound on a system wide basis. + #[pallet::storage] + pub type MaxDelegators = StorageValue<_, u32, OptionQuery>; + + /// Maximum number of delegators that may belong to pool. If `None`, then the count of + /// delegators is not bound on a per pool basis. + #[pallet::storage] + pub type MaxDelegatorsPerPool = StorageValue<_, u32, OptionQuery>; + /// Active delegators. #[pallet::storage] pub type Delegators = CountedStorageMap<_, Twox64Concat, T::AccountId, Delegator>; @@ -904,19 +958,23 @@ pub mod pallet { NotOpen, /// The system is maxed out on pools. MaxPools, + /// Too many delegators in the pool or system. + MaxDelegators, } #[pallet::call] impl Pallet { /// Stake funds with a pool. The amount to bond is transferred from the delegator to the - /// pools account and immediately increases the pools bond. + /// pools account and immediately increases the pools bond. If this call is successful the + /// fee is refunded. + /// + /// # Note /// - /// Notes - /// * an account can only be a member of a single pool. - /// * this will *not* dust the delegator account, so the delegator must have at least + /// * An account can only be a member of a single pool. + /// * This call will *not* dust the delegator account, so the delegator must have at least /// `existential deposit + amount` in their account. /// * Only a pool with [`PoolState::Open`] can be joined - #[pallet::weight(666)] + #[pallet::weight(T::WeightInfo::join())] #[frame_support::transactional] pub fn join( origin: OriginFor, @@ -931,6 +989,7 @@ pub mod pallet { let mut bonded_pool = BondedPool::::get(&pool_account).ok_or(Error::::PoolNotFound)?; bonded_pool.ok_to_join_with(amount)?; + bonded_pool.inc_delegators()?; // We don't actually care about writing the reward pool, we just need its // total earnings at this point in time. @@ -972,6 +1031,7 @@ pub mod pallet { bonded: amount, }); + // Ok(Pays::No.into()) // TBD Ok(()) } @@ -996,23 +1056,23 @@ pub mod pallet { /// Unbond _all_ of the `target` delegators funds from the pool. Under certain conditions, /// this call can be dispatched permissionlessly (i.e. by any account). /// - /// Conditions for a permissionless dispatch: + /// # Conditions for a permissionless dispatch /// - /// - The pool is blocked and the caller is either the root or state-toggler. This is + /// * The pool is blocked and the caller is either the root or state-toggler. This is /// refereed to as a kick. - /// - The pool is destroying and the delegator is not the depositor. - /// - The pool is destroying, the delegator is the depositor and no other delegators are in + /// * The pool is destroying and the delegator is not the depositor. + /// * The pool is destroying, the delegator is the depositor and no other delegators are in /// the pool. /// - /// Conditions for permissioned dispatch (i.e. the caller is also the target): + /// # Conditions for permissioned dispatch (i.e. the caller is also the target): /// - /// - The caller is not the depositor - /// - The caller is the depositor, the pool is destroying and not other delegators are in - /// the pool. + /// * The caller is not the depositor. + /// * The caller is the depositor, the pool is destroying and no other delegators are in the + /// pool. /// /// Note: If their are too many unlocking chunks to unbond with the pool account, /// [`Self::withdraw_unbonded_pool`] can be called to try and minimize unlocking chunks. - #[pallet::weight(666)] + #[pallet::weight(T::WeightInfo::unbond_other())] pub fn unbond_other(origin: OriginFor, target: T::AccountId) -> DispatchResult { let caller = ensure_signed(origin)?; let delegator = Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; @@ -1069,7 +1129,7 @@ pub mod pallet { /// /// This is useful if their are too many unlocking chunks to call `unbond_other`, and some /// can be cleared by withdrawing. - #[pallet::weight(666)] + #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))] pub fn pool_withdraw_unbonded( origin: OriginFor, pool_account: T::AccountId, @@ -1087,17 +1147,19 @@ pub mod pallet { /// Withdraw unbonded funds for the `target` delegator. Under certain conditions, /// this call can be dispatched permissionlessly (i.e. by any account). /// - /// Conditions for a permissionless dispatch: + /// # Conditions for a permissionless dispatch + /// + /// * The pool is in destroy mode and the target is not the depositor. + /// * The target is the depositor and they are the only delegator in the sub pools. + /// * The pool is blocked and the caller is either the root or state-toggler. /// - /// - The pool is in destroy mode and the target is not the depositor. - /// - The target is the depositor and they are the only delegator in the sub pools. - /// - The pool is blocked and the caller is either the root or state-toggler. + /// # Conditions for permissioned dispatch /// - /// Conditions for permissioned dispatch: + /// * The caller is the target and they are not the depositor. /// - /// - The caller is the target and they are not the depositor. + /// # Note /// - /// Note: If the target is the depositor, the pool will be destroyed. + /// If the target is the depositor, the pool will be destroyed. #[pallet::weight( T::WeightInfo::withdraw_unbonded_other_kill(*num_slashing_spans) )] @@ -1111,8 +1173,8 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); ensure!( - current_era.saturating_sub(unbonding_era) >= - T::StakingInterface::bonding_duration(), + current_era.saturating_sub(unbonding_era) + >= T::StakingInterface::bonding_duration(), Error::::NotUnbondedYet ); @@ -1145,9 +1207,9 @@ pub mod pallet { balance_to_unbond } - // In the rare case a pool had such an extreme slash that it erased all the bonded - // balance and balance in unlocking chunks - .min(T::Currency::usable_balance(&delegator.pool)); + // We can get here in the rare case a pool had such an extreme slash that it erased + // all the bonded balance and balance in unlocking chunks + .min(bonded_pool.not_bonded_balance()); T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; @@ -1190,6 +1252,7 @@ pub mod pallet { bonded_pool.remove(); None } else { + bonded_pool.dec_delegators().put(); SubPoolsStorage::::insert(&delegator.pool, sub_pools); Some(T::WeightInfo::withdraw_unbonded_other_update(num_slashing_spans)) }; @@ -1200,17 +1263,17 @@ pub mod pallet { /// Create a new delegation pool. /// - /// # Parameters + /// # Arguments /// - /// * `amount`: The amount of funds to delegate to the pool. This also acts of a sort of + /// * `amount` - The amount of funds to delegate to the pool. This also acts of a sort of /// deposit since the pools creator cannot fully unbond funds until the pool is being /// destroyed. - /// * `index`: A disambiguation index for creating the account. Likely only useful when + /// * `index` - A disambiguation index for creating the account. Likely only useful when /// creating multiple pools in the same extrinsic. - /// * `root`: The account to set as [`BondedPool::root`]. - /// * `nominator`: The account to set as the [`BondedPool::nominator`]. - /// * `state_toggler`: The account to set as the [`BondedPool::state_toggler`]. - #[pallet::weight(666)] + /// * `root` - The account to set as [`BondedPool::root`]. + /// * `nominator` - The account to set as the [`BondedPool::nominator`]. + /// * `state_toggler` - The account to set as the [`BondedPool::state_toggler`]. + #[pallet::weight(T::WeightInfo::create())] #[frame_support::transactional] pub fn create( origin: OriginFor, @@ -1222,8 +1285,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() && - amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() + && amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1237,6 +1300,7 @@ pub mod pallet { let mut bonded_pool = BondedPool:: { account: pool_account.clone(), points: Zero::zero(), + delegator_counter: Zero::zero(), depositor: who.clone(), root, nominator, @@ -1328,8 +1392,12 @@ pub mod pallet { impl Pallet { fn create_accounts(index: u16) -> (T::AccountId, T::AccountId) { let parent_hash = frame_system::Pallet::::parent_hash(); + // let parent_hash = frame_system::Pallet::::block_number(); // TODO let ext_index = frame_system::Pallet::::extrinsic_index().unwrap_or_default(); + // TODO use a transparent prefix before the hashed part + // (b"nom-pools/bonded", (index, parent_hash, ext_index).using_encoded(blake2_256)) + // (b"nom-pools/rewards", (index, parent_hash, ext_index).using_encoded(blake2_256)) let stash_entropy = (b"pools/stash", index, parent_hash, ext_index).using_encoded(blake2_256); let reward_entropy = @@ -1378,9 +1446,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() || - current_points.is_zero() || - reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() + || current_points.is_zero() + || reward_pool.balance.is_zero() { Zero::zero() } else { @@ -1444,7 +1512,7 @@ impl Pallet { impl OnStakerSlash> for Pallet { fn on_slash( pool_account: &T::AccountId, - _slashed_bonded: BalanceOf, + _slashed_bonded: BalanceOf, // bonded balance is always read directly from staking. slashed_unlocking: &BTreeMap>, ) { let mut sub_pools = match SubPoolsStorage::::get(pool_account) { diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 73b2674d60263..63283bf432e75 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -239,6 +239,10 @@ fn post_checks() { assert_eq!(RewardPools::::count(), BondedPools::::count()); assert!(SubPoolsStorage::::count() <= BondedPools::::count()); assert!(Delegators::::count() >= BondedPools::::count()); + // TODO + // MaxDelegator === Delegator::count + // Each pool has most max delegators per pool + // sum of all pools delegator counts } pub(crate) fn unsafe_set_state(pool_account: &AccountId, state: PoolState) -> Result<(), ()> { diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 7587d97d80df2..35cb8b936ae97 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -530,12 +530,12 @@ impl StakingLedger { /// Slashes are computed by: /// /// 1) Balances of the unlocking chunks in range `slash_era + 1..=apply_era` are summed and - /// stored in `total_balance_affected`. 2) `slash_ratio` is computed as `slash_amount / - /// total_balance_affected`. 3) `Ledger::active` is set to `(1- slash_ratio) * Ledger::active`. - /// 4) For all unlocking chunks in range `slash_era + 1..=apply_era` set their balance to `(1 - - /// slash_ratio) * unbonding_pool_balance`. - /// 5) Slash any remaining slash amount from the remaining chunks, starting with the `slash_era` - /// and going backwards. + /// stored in `total_balance_affected`. + /// 2) `slash_ratio` is computed as `slash_amount / total_balance_affected`. 3) `Ledger::active` + /// is set to `(1- slash_ratio) * Ledger::active`. 4) For all unlocking chunks in range + /// `slash_era + 1..=apply_era` set their balance to `(1 - slash_ratio) * + /// unbonding_pool_balance`. 5) Slash any remaining slash amount from the remaining chunks, + /// starting with the `slash_era` and going backwards. fn slash( &mut self, slash_amount: BalanceOf, @@ -549,7 +549,7 @@ impl StakingLedger { let mut remaining_slash = slash_amount; let pre_slash_total = self.total; - // The index of the first chunk after the slash (OR the last index if the + // The index of the first chunk after the slash era let start_index = self.unlocking.partition_point(|c| c.era < slash_era); // The indices of from the first chunk after the slash up through the most recent chunk. // (The most recent chunk is at greatest from this era) @@ -581,11 +581,12 @@ impl StakingLedger { let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { // We don't want the added complexity of using extended ints, so if this saturates we // will always just try and slash as much as possible. - let maybe_numerator = slash_amount.checked_mul(target); + let maybe_numerator = slash_amount.checked_mul(target); // Use a Perbill println!("{:?}=maybe_numerator", maybe_numerator); // Calculate the amount to slash from the target let slash_from_target = match (maybe_numerator, is_proportional_slash) { + // Perbill::from_rational(slash_amount, affected_balance) * target // Equivalent to `(slash_amount / affected_balance) * target`. (Some(numerator), true) => numerator.div(affected_balance), (None, _) | (_, false) => (*slash_remaining).min(*target), diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index c161f96bc6251..c452294e71ec8 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -235,6 +235,7 @@ const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = parameter_types! { pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; pub static MaxNominations: u32 = 16; + pub static LedgerSlashPerEra: (BalanceOf, HashMap) = HashMap::new(); } impl pallet_bags_list::Config for Test { @@ -249,6 +250,17 @@ impl onchain::Config for Test { type DataProvider = Staking; } +struct OnStakerSlashMock(PhantomData); +impl OnStakerSlash> for OnStakerSlashMock { + fn on_slash( + pool_account: &AccountId, + slashed_bonded: Balance, + slashed_chunks: &BTreeMap, + ) { + LedgerSlashPerEra::put((slashed_bonded, slashed_chunks)); + } +} + impl crate::pallet::pallet::Config for Test { type MaxNominations = MaxNominations; type Currency = Balances; diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 3eb8c307bff9e..8ff0eea7f20bc 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4747,8 +4747,6 @@ fn force_apply_min_commission_works() { }); } -// TODO: adapt these for the staking use case - #[test] fn ledger_slash_works() { let c = |era, value| UnlockChunk:: { era, value }; diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index c4df8de17b0a8..b924b688ce9d2 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -36,7 +36,7 @@ pub trait OnStakerSlash { /// /// # Arguments /// - /// * `stash` - the staker whom the slash was applied to. + /// * `stash` - The stash of the staker whom the slash was applied to. /// * `slashed_bonded` - The new bonded balance of the staker after the slash was applied. /// * `slashed_unlocking` - A map from eras that the staker is unbonding in to the new balance /// after the slash was applied. @@ -53,69 +53,6 @@ impl OnStakerSlash for () { } } -/// Arguments for [`PoolsInterface::slash_pool`]. -// pub struct AccountSlashCalcArgs<'a, AccountId, Balance> { -// /// _Stash_ account of the account to slash. -// pub pool_stash: &'a AccountId, -// /// The amount to slash. -// pub slash_amount: Balance, -// /// Era the slash happened in. -// pub slash_era: EraIndex, -// /// Era the slash is applied in. -// pub apply_era: EraIndex, -// /// The current active bonded of the account (i.e. `StakingLedger::active`) -// pub active_bonded: Balance, -// /// Unlocking funds -// pub unlocking: BTreeMap, -// } - -// /// Output for [`AccountSlashCalc::calc_slash_distribution`]. -// pub struct AccountSlashCalcOut { -// /// The new active bonded balance of the stash with the proportional slash amounted subtracted. -// pub slashed_bonded: Balance, -// /// A map from era of unlocking chunks to their new balance with the proportional slash amount -// /// subtracted. -// pub slashed_unlocking: BTreeMap, -// } - -/// Something that calculates the new balances of a staked account that has had a slash applied to -/// it. -// pub trait AccountSlashCalc { -// type AccountId; -// type Balance; - -// // The current approach here is to share `BTreeMap>` with the staking -// // API. This is arguably a leaky, suboptimal API because both sides have to share this -// // non-trivial data structure. With the current design we do this because we track the unbonding -// // balance in both the pallet-staking `unlocking` chunks and in here with the pallet-pools -// // `SubPools`. Because both pallets need to know about slashes to unbonding funds we either have -// // to replicate the slashing logic between the pallets, or share some data. A ALTERNATIVE is -// // having the pallet-pools read the unbonding balance per era directly from pallet-staking. The -// // downside of this is that once a delegator calls `withdraw_unbonded`, the chunk is removed and -// // we can't keep track of the balance for that `UnbondPool` anymore, thus we must merge the -// // balance and points of that `UnbondPool` with the `no_era` pool immediately upon calling -// // withdraw_unbonded. We choose not to do this because if there was a slash, it would negatively -// // affect the points:balance ratio of the `no_era` pool for everyone, including those who may -// // not have been unbonding in eras effected by the slash. -// /// Calculate the distribution of a slash for the given account. The distribution can be over -// /// the bonded balance of the account, and the unbonding balance per era. -// fn calc_slash_distribution( -// args: SlashPoolArgs, -// ) -> Option>; -// } - -// pub trait PoolsInterface { -// type AccountId; -// type Balance; - -// /// Calculate the slashes for each unbonding chunk/unbonding pool and the actively bonded -// /// balance. This should apply the updated balances to the pools and return the updated balances -// /// to the caller (presumably pallet-staking) so they can do the corresponding updates. -// fn slash_pool( -// args: SlashPoolArgs, -// ) -> Option>; -// } - /// Trait for communication with the staking pallet. pub trait StakingInterface { /// Balance type used by the staking system. From 106a885ca72386f52e631547ce32360237c080c4 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 2 Mar 2022 14:38:05 -0800 Subject: [PATCH 144/299] Introduce counter for delegators --- .../nomination-pools/benchmarking/src/lib.rs | 4 + .../nomination-pools/benchmarking/src/mock.rs | 2 +- frame/nomination-pools/src/lib.rs | 87 ++++++++++--------- frame/nomination-pools/src/mock.rs | 18 +++- frame/nomination-pools/src/tests.rs | 59 +++++++++---- frame/staking/src/benchmarking.rs | 1 - frame/staking/src/pallet/impls.rs | 4 + primitives/staking/src/lib.rs | 4 + 8 files changed, 119 insertions(+), 60 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 66098668b4692..86c09555ae4b3 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -484,6 +484,7 @@ frame_benchmarking::benchmarks! { nominator: depositor.clone(), state_toggler: depositor.clone(), state: PoolState::Open, + delegator_counter: 1 } ); assert_eq!( @@ -524,6 +525,7 @@ frame_benchmarking::benchmarks! { nominator: depositor.clone(), state_toggler: depositor.clone(), state: PoolState::Open, + delegator_counter: 1, } ); assert_eq!( @@ -533,6 +535,8 @@ frame_benchmarking::benchmarks! { } } +// TODO: consider benchmarking slashing logic with pools + frame_benchmarking::impl_benchmark_test_suite!( Pallet, crate::mock::new_test_ext(), diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index b5aa0727d8cf6..8a12517e7f0f8 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -108,7 +108,7 @@ impl pallet_staking::Config for Runtime { frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; type GenesisElectionProvider = Self::ElectionProvider; type SortedListProvider = pallet_bags_list::Pallet; - type PoolsInterface = Pools; + type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 52516e7102705..40eab44524297 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -298,11 +298,12 @@ // TODO // - Counter for delegators per pool and allow limiting of delegators per pool // - tests for the above +// - counter for delegators should never be below 1 (below 1 means the pool should be destroyed) // - write detailed docs for StakingInterface // - various back ports // - test delegator counter -// - slashing - test for correct input to hook -// - transparent account ids +// - slashing - test for correct input to hook (these tests need to be in staking side) +// - transparent account ids - prefix with pls/rewd and pls/stsh, also use PalletId for entropy // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -311,13 +312,17 @@ use frame_support::{ ensure, pallet_prelude::*, storage::bounded_btree_map::BoundedBTreeMap, - traits::{Currency, DefensiveOption, DefensiveResult, ExistenceRequirement, Get}, + traits::{ + Currency, DefensiveOption, DefensiveResult, DefensiveSaturating, ExistenceRequirement, Get, + }, DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_core::U256; use sp_io::hashing::blake2_256; -use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZeroInput, Zero}; +use sp_runtime::traits::{ + Bounded, Convert, One, Saturating, StaticLookup, TrailingZeroInput, Zero, +}; use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div, vec::Vec}; @@ -551,7 +556,6 @@ impl BondedPool { } fn can_kick(&self, who: &T::AccountId) -> bool { - let (root, toggler, state) = (self.root, self.state_toggler, self.state); *who == self.root || *who == self.state_toggler && self.state == PoolState::Blocked } @@ -638,26 +642,27 @@ impl BondedPool { /// Increment the delegator counter. Ensures that the pool and system delegator limits are /// respected. - fn inc_delegators(&self) -> Result<(), DispatchError> { + fn inc_delegators(&mut self) -> Result<(), DispatchError> { if let Some(max_per_pool) = MaxDelegatorsPerPool::::get() { ensure!(self.delegator_counter < max_per_pool, Error::::MaxDelegators); } if let Some(max) = MaxDelegators::::get() { ensure!(Delegators::::count() < max, Error::::MaxDelegators); } - self.delegator_counter += 1; + self.delegator_counter = self.delegator_counter.defensive_saturating_add(1); Ok(()) } /// Decrement the delegator counter. - fn dec_delegators(self) -> Self { - self.delegator_counter -= 1; + fn dec_delegators(mut self) -> Self { + self.delegator_counter = self.delegator_counter.defensive_saturating_sub(1); self } - fn non_bonded_balance(&self) -> BalanceOf { + /// The pools balance that is not locked. This assumes the staking system is the only + fn non_locked_balance(&self) -> BalanceOf { T::Currency::free_balance(&self.account).saturating_sub( - T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()), + T::StakingInterface::locked_balance(&self.account).unwrap_or(Zero::zero()), ) } } @@ -1185,35 +1190,36 @@ pub mod pallet { let should_remove_pool = bonded_pool .ok_to_withdraw_unbonded_other_with(&caller, &target, &delegator, &sub_pools)?; - let balance_to_unbond = - if let Some(pool) = sub_pools.with_era.get_mut(&unbonding_era) { - let balance_to_unbond = pool.balance_to_unbond(delegator.points); - pool.points = pool.points.saturating_sub(delegator.points); - pool.balance = pool.balance.saturating_sub(balance_to_unbond); - if pool.points.is_zero() { - // Clean up pool that is no longer used - sub_pools.with_era.remove(&unbonding_era); - } - - balance_to_unbond - } else { - // A pool does not belong to this era, so it must have been merged to the - // era-less pool. - let balance_to_unbond = sub_pools.no_era.balance_to_unbond(delegator.points); - sub_pools.no_era.points = - sub_pools.no_era.points.saturating_sub(delegator.points); - sub_pools.no_era.balance = - sub_pools.no_era.balance.saturating_sub(balance_to_unbond); - - balance_to_unbond + // Before calculate the `balance_to_unbond`, with call withdraw unbonded to ensure the + // `non_locked_balance` is correct. + T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; + + let balance_to_unbond = if let Some(pool) = sub_pools.with_era.get_mut(&unbonding_era) { + let balance_to_unbond = pool.balance_to_unbond(delegator.points); + pool.points = pool.points.saturating_sub(delegator.points); + pool.balance = pool.balance.saturating_sub(balance_to_unbond); + if pool.points.is_zero() { + // Clean up pool that is no longer used + sub_pools.with_era.remove(&unbonding_era); } - // We can get here in the rare case a pool had such an extreme slash that it erased - // all the bonded balance and balance in unlocking chunks - .min(bonded_pool.not_bonded_balance()); - T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; + balance_to_unbond + } else { + // A pool does not belong to this era, so it must have been merged to the + // era-less pool. + let balance_to_unbond = sub_pools.no_era.balance_to_unbond(delegator.points); + sub_pools.no_era.points = sub_pools.no_era.points.saturating_sub(delegator.points); + sub_pools.no_era.balance = + sub_pools.no_era.balance.saturating_sub(balance_to_unbond); + + balance_to_unbond + } + // TODO: make sure this is a test for this edge case + // We can get here in the rare case a pool had such an extreme slash that it erased + // all the bonded balance and balance in unlocking chunks + .min(bonded_pool.non_locked_balance()); - if balance_to_unbond <= T::Currency::minimum_balance() { + if balance_to_unbond >= T::Currency::minimum_balance() { T::Currency::transfer( &delegator.pool, &target, @@ -1227,7 +1233,7 @@ pub mod pallet { amount: balance_to_unbond, }); } else { - // This should only happen if 1) a previous withdraw put the pools balance + //This should only happen if 1) a previous withdraw put the pools balance // below ED and it was dusted or 2) the pool was slashed a huge amount that wiped // all the unlocking chunks and bonded balance, thus causing inconsistencies with // unbond pool's tracked balance and the actual balance (if this happens, the pool @@ -1250,6 +1256,7 @@ pub mod pallet { T::Currency::make_free_balance_be(&reward_pool.account, Zero::zero()); T::Currency::make_free_balance_be(&bonded_pool.account, Zero::zero()); bonded_pool.remove(); + // TODO: destroy event None } else { bonded_pool.dec_delegators().put(); @@ -1300,7 +1307,7 @@ pub mod pallet { let mut bonded_pool = BondedPool:: { account: pool_account.clone(), points: Zero::zero(), - delegator_counter: Zero::zero(), + delegator_counter: One::one(), depositor: who.clone(), root, nominator, @@ -1339,6 +1346,8 @@ pub mod pallet { }, ); + // TODO: event + Ok(()) } diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 63283bf432e75..c1189f745aafc 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -47,6 +47,17 @@ impl sp_staking::StakingInterface for StakingMock { BondedBalanceMap::get().get(who).map(|v| *v) } + fn locked_balance(who: &Self::AccountId) -> Option { + match ( + UnbondingBalanceMap::get().get(who).map(|v| *v), + BondedBalanceMap::get().get(who).map(|v| *v), + ) { + (None, None) => None, + (Some(v), None) | (None, Some(v)) => Some(v), + (Some(a), Some(b)) => Some(a + b), + } + } + fn bond_extra(who: Self::AccountId, extra: Self::Balance) -> DispatchResult { BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(&who).unwrap() += extra); Ok(()) @@ -60,10 +71,9 @@ impl sp_staking::StakingInterface for StakingMock { } fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result { - let maybe_new_free = UNBONDING_BALANCE_MAP.with(|m| m.borrow_mut().remove(&who)); - if let Some(new_free) = maybe_new_free { - assert_ok!(Balances::mutate_account(&who, |a| a.free += new_free)); - } + // Simulates removing unlocking chunks and only having the bonded balance locked + let _maybe_new_free = UNBONDING_BALANCE_MAP.with(|m| m.borrow_mut().remove(&who)); + Ok(100) } diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 5dd5e1e7862fb..742b7a2cd7682 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1,8 +1,7 @@ use super::*; use crate::mock::{ - unsafe_set_state, Balance, Balances, BondingDuration, CurrentEra, ExistentialDeposit, - ExtBuilder, Nominations, Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, - UNBONDING_BALANCE_MAP, + unsafe_set_state, Balance, Balances, CurrentEra, ExistentialDeposit, ExtBuilder, Nominations, + Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, UNBONDING_BALANCE_MAP, }; use frame_support::{assert_noop, assert_ok}; @@ -32,6 +31,7 @@ fn test_setup_works() { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 1, } ); assert_eq!( @@ -77,6 +77,7 @@ mod bonded_pool { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 1, }; // 1 points : 1 balance ratio @@ -129,6 +130,7 @@ mod bonded_pool { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 1, }; StakingMock::set_bonded_balance(123, 100); assert_eq!(bonded_pool.balance_to_unbond(10), 10); @@ -175,6 +177,7 @@ mod bonded_pool { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 1, }; // Simulate a 100% slashed pool @@ -356,7 +359,7 @@ mod join { #[test] fn join_works() { - let bonded = |points| BondedPool:: { + let bonded = |points, delegator_counter| BondedPool:: { depositor: 10, state: PoolState::Open, points, @@ -364,6 +367,7 @@ mod join { root: 900, nominator: 901, state_toggler: 902, + delegator_counter, }; ExtBuilder::default().build_and_execute(|| { // Given @@ -383,7 +387,7 @@ mod join { unbonding_era: None } ); - assert_eq!(BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), bonded(12)); + assert_eq!(BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), bonded(12, 2)); // Given // The bonded balance is slashed in half @@ -405,7 +409,7 @@ mod join { unbonding_era: None } ); - assert_eq!(BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), bonded(12 + 24)); + assert_eq!(BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), bonded(12 + 24, 3)); }); } @@ -434,6 +438,7 @@ mod join { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 1, } .put(); // Force the points:balance ratio to 100/10 (so 10) @@ -484,11 +489,20 @@ mod join { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 1, } .put(); let _ = Pools::join(Origin::signed(11), 420, 123); }); } + + fn join_max_delegator_limits_are_respected() { + todo!() + } + + fn join_max_pools_limits_are_respected() { + todo!() + } } mod claim_payout { @@ -1192,6 +1206,7 @@ mod unbond { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 1, } ); @@ -1227,6 +1242,7 @@ mod unbond { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 3, } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 94); @@ -1252,6 +1268,7 @@ mod unbond { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 3, } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 2); @@ -1276,6 +1293,7 @@ mod unbond { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 3, } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); @@ -1355,7 +1373,8 @@ mod unbond { account: PRIMARY_ACCOUNT, depositor: 10, state: PoolState::Blocked, - points: 10 // Only 10 points because 200 + 100 was unbonded + points: 10, // Only 10 points because 200 + 100 was unbonded + delegator_counter: 3, } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 10); @@ -1474,6 +1493,7 @@ mod unbond { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 1, } .put(); @@ -1491,13 +1511,17 @@ mod pool_withdraw_unbonded { // Given 10 unbond'ed directly against the pool account assert_ok!(StakingMock::unbond(PRIMARY_ACCOUNT, 5)); // and the pool account only has 10 balance - assert_eq!(Balances::free_balance(PRIMARY_ACCOUNT), 10); + assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), Some(5)); + assert_eq!(StakingMock::locked_balance(&PRIMARY_ACCOUNT), Some(10)); + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 10); // When assert_ok!(Pools::pool_withdraw_unbonded(Origin::signed(10), PRIMARY_ACCOUNT, 0)); - // Then there unbonding balance becomes free - assert_eq!(Balances::free_balance(PRIMARY_ACCOUNT), 15); + // Then there unbonding balance is no longer locked + assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), Some(5)); + assert_eq!(StakingMock::locked_balance(&PRIMARY_ACCOUNT), Some(5)); + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 10); }); } } @@ -1511,9 +1535,9 @@ mod withdraw_unbonded_other { .add_delegators(vec![(40, 40), (550, 550)]) .build_and_execute(|| { // Given - Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 0); assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 600); let mut current_era = 1; CurrentEra::set(current_era); @@ -1526,13 +1550,13 @@ mod withdraw_unbonded_other { // Sanity check assert_eq!(*unbond_pool, UnbondPool { points: 10, balance: 10 }); - // Simulate a slash to the pool with_era(current_era) decreasing the balance by half + // Simulate a slash to the pool with_era(current_era), decreasing the balance by half unbond_pool.balance = 5; SubPoolsStorage::::insert(PRIMARY_ACCOUNT, sub_pools); - // Update the equivalent of the unbonding chunks for the `StakingMock` UNBONDING_BALANCE_MAP .with(|m| *m.borrow_mut().get_mut(&PRIMARY_ACCOUNT).unwrap() -= 5); + Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 595); // Advance the current_era to ensure all `with_era` pools will be merged into // `no_era` pool @@ -1600,7 +1624,7 @@ mod withdraw_unbonded_other { .build_and_execute(|| { // Given StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); // slash bonded balance - Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 0); + Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 100); assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); @@ -1719,6 +1743,7 @@ mod withdraw_unbonded_other { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 3, } ); CurrentEra::set(StakingMock::bonding_duration()); @@ -1770,9 +1795,11 @@ mod withdraw_unbonded_other { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 2, } ); CurrentEra::set(StakingMock::bonding_duration()); + assert_eq!(Balances::free_balance(100), 100); // Cannot permissionlessly withdraw assert_noop!( @@ -1967,7 +1994,8 @@ mod create { account: stash.clone(), root: 123, nominator: 456, - state_toggler: 789 + state_toggler: 789, + delegator_counter: 1, } ); assert_eq!(StakingMock::bonded_balance(&stash).unwrap(), StakingMock::minimum_bond()); @@ -2018,6 +2046,7 @@ mod create { root: 900, nominator: 901, state_toggler: 902, + delegator_counter: 1, } .put(); assert_eq!(MaxPools::::get(), Some(2)); diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index d143f135e92f9..76d0c55298cd3 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -808,7 +808,6 @@ benchmarks! { &mut BalanceOf::::zero(), &mut NegativeImbalanceOf::::zero(), 0, - 0 ); } verify { let balance_after = T::Currency::free_balance(&stash); diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 109a59362cb68..f0cc09139f848 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1336,6 +1336,10 @@ impl StakingInterface for Pallet { Self::ledger(controller).map(|l| l.active) } + fn locked_balance(controller: &Self::AccountId) -> Option { + Self::ledger(controller).map(|l| l.total) + } + fn bond_extra(stash: Self::AccountId, extra: Self::Balance) -> DispatchResult { Self::bond_extra(RawOrigin::Signed(stash).into(), extra) } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index b924b688ce9d2..00e905331bb71 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -78,6 +78,10 @@ pub trait StakingInterface { /// Balance `controller` has bonded for nominating. fn bonded_balance(controller: &Self::AccountId) -> Option; + /// Balance `controller` has locked by the staking system. This is the bonded funds and the + /// unlocking funds and thus is a superset of bonded funds. + fn locked_balance(controller: &Self::AccountId) -> Option; + fn bond_extra(controller: Self::AccountId, extra: Self::Balance) -> DispatchResult; fn unbond(controller: Self::AccountId, value: Self::Balance) -> DispatchResult; From 5a556fb00c54674b5fbb2ecaecac7f55b80599e7 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 2 Mar 2022 16:05:08 -0800 Subject: [PATCH 145/299] Add tests for max delegator errors --- .../nomination-pools/benchmarking/src/mock.rs | 2 + frame/nomination-pools/src/lib.rs | 101 ++++++++++-------- frame/nomination-pools/src/mock.rs | 2 + frame/nomination-pools/src/tests.rs | 54 +++++++++- 4 files changed, 112 insertions(+), 47 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 8a12517e7f0f8..e19bc8a792740 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -185,6 +185,8 @@ pub fn new_test_ext() -> sp_io::TestExternalities { min_join_bond: 2, min_create_bond: 2, max_pools: Some(3), + max_delegators_per_pool: Some(3), + max_delegators: Some(3 * 3), } .assimilate_storage(&mut storage); sp_io::TestExternalities::from(storage) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 40eab44524297..eb35279556777 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -320,9 +320,7 @@ use frame_support::{ use scale_info::TypeInfo; use sp_core::U256; use sp_io::hashing::blake2_256; -use sp_runtime::traits::{ - Bounded, Convert, One, Saturating, StaticLookup, TrailingZeroInput, Zero, -}; +use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZeroInput, Zero}; use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div, vec::Vec}; @@ -351,9 +349,8 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => { - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) - }, + (true, true) | (false, true) => + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -378,7 +375,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero(); + return Zero::zero() } // Equivalent of (current_balance / current_points) * delegator_points @@ -542,8 +539,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) - < BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) < + BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -745,7 +742,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self; + return self } // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -890,12 +887,20 @@ pub mod pallet { pub min_join_bond: BalanceOf, pub min_create_bond: BalanceOf, pub max_pools: Option, + pub max_delegators_per_pool: Option, + pub max_delegators: Option, } #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { - Self { min_join_bond: Zero::zero(), min_create_bond: Zero::zero(), max_pools: Some(10) } + Self { + min_join_bond: Zero::zero(), + min_create_bond: Zero::zero(), + max_pools: Some(16), + max_delegators_per_pool: Some(32), + max_delegators: Some(16 * 32), + } } } @@ -907,6 +912,12 @@ pub mod pallet { if let Some(max_pools) = self.max_pools { MaxPools::::put(max_pools); } + if let Some(max_delegators_per_pool) = self.max_delegators_per_pool { + MaxDelegatorsPerPool::::put(max_delegators_per_pool); + } + if let Some(max_delegators) = self.max_delegators { + MaxDelegators::::put(max_delegators); + } } } @@ -1178,8 +1189,8 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); ensure!( - current_era.saturating_sub(unbonding_era) - >= T::StakingInterface::bonding_duration(), + current_era.saturating_sub(unbonding_era) >= + T::StakingInterface::bonding_duration(), Error::::NotUnbondedYet ); @@ -1194,30 +1205,32 @@ pub mod pallet { // `non_locked_balance` is correct. T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; - let balance_to_unbond = if let Some(pool) = sub_pools.with_era.get_mut(&unbonding_era) { - let balance_to_unbond = pool.balance_to_unbond(delegator.points); - pool.points = pool.points.saturating_sub(delegator.points); - pool.balance = pool.balance.saturating_sub(balance_to_unbond); - if pool.points.is_zero() { - // Clean up pool that is no longer used - sub_pools.with_era.remove(&unbonding_era); + let balance_to_unbond = + if let Some(pool) = sub_pools.with_era.get_mut(&unbonding_era) { + let balance_to_unbond = pool.balance_to_unbond(delegator.points); + pool.points = pool.points.saturating_sub(delegator.points); + pool.balance = pool.balance.saturating_sub(balance_to_unbond); + if pool.points.is_zero() { + // Clean up pool that is no longer used + sub_pools.with_era.remove(&unbonding_era); + } + + balance_to_unbond + } else { + // A pool does not belong to this era, so it must have been merged to the + // era-less pool. + let balance_to_unbond = sub_pools.no_era.balance_to_unbond(delegator.points); + sub_pools.no_era.points = + sub_pools.no_era.points.saturating_sub(delegator.points); + sub_pools.no_era.balance = + sub_pools.no_era.balance.saturating_sub(balance_to_unbond); + + balance_to_unbond } - - balance_to_unbond - } else { - // A pool does not belong to this era, so it must have been merged to the - // era-less pool. - let balance_to_unbond = sub_pools.no_era.balance_to_unbond(delegator.points); - sub_pools.no_era.points = sub_pools.no_era.points.saturating_sub(delegator.points); - sub_pools.no_era.balance = - sub_pools.no_era.balance.saturating_sub(balance_to_unbond); - - balance_to_unbond - } - // TODO: make sure this is a test for this edge case - // We can get here in the rare case a pool had such an extreme slash that it erased - // all the bonded balance and balance in unlocking chunks - .min(bonded_pool.non_locked_balance()); + // TODO: make sure this is a test for this edge case + // We can get here in the rare case a pool had such an extreme slash that it erased + // all the bonded balance and balance in unlocking chunks + .min(bonded_pool.non_locked_balance()); if balance_to_unbond >= T::Currency::minimum_balance() { T::Currency::transfer( @@ -1292,8 +1305,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() - && amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() && + amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1307,13 +1320,15 @@ pub mod pallet { let mut bonded_pool = BondedPool:: { account: pool_account.clone(), points: Zero::zero(), - delegator_counter: One::one(), + delegator_counter: Zero::zero(), depositor: who.clone(), root, nominator, state_toggler, state: PoolState::Open, }; + // Increase the delegator counter to account for depositor; checks delegator limits. + bonded_pool.inc_delegators()?; // We must calculate the points issued *before* we bond who's funds, else // points:balance ratio will be wrong. let points_issued = bonded_pool.issue(amount); @@ -1405,6 +1420,8 @@ impl Pallet { let ext_index = frame_system::Pallet::::extrinsic_index().unwrap_or_default(); // TODO use a transparent prefix before the hashed part + // (b"identifier/stash", (identifier, who).using_encoded(blake2_256)) + // (b"identifier/rewards", (identifier, who).using_encoded(blake2_256)) // (b"nom-pools/bonded", (index, parent_hash, ext_index).using_encoded(blake2_256)) // (b"nom-pools/rewards", (index, parent_hash, ext_index).using_encoded(blake2_256)) let stash_entropy = @@ -1455,9 +1472,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() - || current_points.is_zero() - || reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() || + current_points.is_zero() || + reward_pool.balance.is_zero() { Zero::zero() } else { diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index c1189f745aafc..0abda4afd2a46 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -199,6 +199,8 @@ impl ExtBuilder { min_join_bond: 2, min_create_bond: 2, max_pools: Some(2), + max_delegators_per_pool: Some(3), + max_delegators: Some(4), } .assimilate_storage(&mut storage); diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 742b7a2cd7682..483c5a2785f3d 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -496,12 +496,42 @@ mod join { }); } + #[test] fn join_max_delegator_limits_are_respected() { - todo!() - } + ExtBuilder::default().build_and_execute(|| { + // Given + assert_eq!(MaxDelegatorsPerPool::::get(), Some(3)); + for i in 1..3 { + let account = i + 100; + Balances::make_free_balance_be(&account, 100 + Balances::minimum_balance()); + + assert_ok!(Pools::join(Origin::signed(account), 100, PRIMARY_ACCOUNT)); + } + + Balances::make_free_balance_be(&103, 100 + Balances::minimum_balance()); + + // Then + assert_noop!( + Pools::join(Origin::signed(103), 100, PRIMARY_ACCOUNT), + Error::::MaxDelegators + ); + + // Given + assert_eq!(Delegators::::count(), 3); + assert_eq!(MaxDelegators::::get(), Some(4)); + Balances::make_free_balance_be(&104, 100 + Balances::minimum_balance()); + assert_ok!(Pools::create(Origin::signed(104), 100, 1, 104, 104, 104)); + let pool_account = BondedPools::::iter() + .find(|(_, bonded_pool)| bonded_pool.depositor == 104) + .map(|(pool_account, _)| pool_account) + .unwrap(); - fn join_max_pools_limits_are_respected() { - todo!() + // Then + assert_noop!( + Pools::join(Origin::signed(103), 100, pool_account), + Error::::MaxDelegators + ); + }); } } @@ -1550,7 +1580,8 @@ mod withdraw_unbonded_other { // Sanity check assert_eq!(*unbond_pool, UnbondPool { points: 10, balance: 10 }); - // Simulate a slash to the pool with_era(current_era), decreasing the balance by half + // Simulate a slash to the pool with_era(current_era), decreasing the balance by + // half unbond_pool.balance = 5; SubPoolsStorage::::insert(PRIMARY_ACCOUNT, sub_pools); // Update the equivalent of the unbonding chunks for the `StakingMock` @@ -2038,6 +2069,7 @@ mod create { Error::::MinimumBondNotMet ); + // Given BondedPool:: { depositor: 10, state: PoolState::Open, @@ -2052,10 +2084,22 @@ mod create { assert_eq!(MaxPools::::get(), Some(2)); assert_eq!(BondedPools::::count(), 2); + // Then assert_noop!( Pools::create(Origin::signed(11), 20, 42, 123, 456, 789), Error::::MaxPools ); + + // Given + assert_eq!(Delegators::::count(), 1); + MaxPools::::put(3); + MaxDelegators::::put(1); + + // Then + assert_noop!( + Pools::create(Origin::signed(11), 20, 0, 11, 11, 11), + Error::::MaxPools + ); }); } } From 967407780079479e11577ee3dfe35a3a6ee0ad68 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 2 Mar 2022 17:56:12 -0800 Subject: [PATCH 146/299] Reproducible account ids --- frame/nomination-pools/src/lib.rs | 100 +++++++++++++++--------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index eb35279556777..a01d9452604c9 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -315,11 +315,10 @@ use frame_support::{ traits::{ Currency, DefensiveOption, DefensiveResult, DefensiveSaturating, ExistenceRequirement, Get, }, - DefaultNoBound, RuntimeDebugNoBound, + DefaultNoBound, PalletId, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_core::U256; -use sp_io::hashing::blake2_256; use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZeroInput, Zero}; use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div, vec::Vec}; @@ -340,6 +339,8 @@ pub type SubPoolsWithEra = BoundedBTreeMap, TotalUnbo type RewardPoints = U256; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; +const BONDED_ACCOUNT_INDEX: [u8; 4] = *b"bond"; +const REWARD_ACCOUNT_INDEX: [u8; 4] = *b"rewd"; /// Calculate the number of points to issue from a pool as `(current_points / current_balance) * /// new_funds` except for some zero edge cases; see logic and tests for details. @@ -435,7 +436,9 @@ pub struct BondedPoolStorage { } #[derive(RuntimeDebugNoBound)] +// #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] +// #[cfg_attr(feature = "std", derive(Clone,))] pub struct BondedPool { /// Points of the pool. Each delegator has some corresponding the points. The portion of points /// that belong to a delegator represent the portion of the pools bonded funds belong to the @@ -456,11 +459,28 @@ pub struct BondedPool { /// Can toggle the pools state, including setting the pool as blocked or putting the pool into /// destruction mode. The state toggle can also "kick" delegators by unbonding them. state_toggler: T::AccountId, - /// AccountId of the pool. + // /// AccountId of the pool. account: T::AccountId, } impl BondedPool { + fn new( + depositor: T::AccountId, + root: T::AccountId, + nominator: T::AccountId, + state_toggler: T::AccountId, + ) -> Self { + Self { + account: Self::create_account(BONDED_ACCOUNT_INDEX, depositor.clone()), + depositor, + root, + nominator, + state_toggler, + state: PoolState::Open, + points: Zero::zero(), + delegator_counter: Zero::zero(), + } + } /// Get [`Self`] from storage. Returns `None` if no entry for `pool_account` exists. fn get(pool_account: &T::AccountId) -> Option { BondedPools::::try_get(pool_account).ok().map(|storage| Self { @@ -490,12 +510,23 @@ impl BondedPool { }, ); } - /// Consume and remove [`Self`] from storage. fn remove(self) { BondedPools::::remove(self.account); } + fn create_account(index: [u8; 4], depositor: T::AccountId) -> T::AccountId { + (b"npls", index, depositor.clone()).using_encoded(|encoded| { + let trimmed = &encoded[..T::AccountId::max_encoded_len()]; + Decode::decode(&mut TrailingZeroInput::new(trimmed)) + .expect("Input is trimmed to the max encoded length. qed") + }) + } + + fn reward_account(&self) -> T::AccountId { + Self::create_account(REWARD_ACCOUNT_INDEX, self.depositor.clone()) + } + /// Get the amount of points to issue for some new funds that will be bonded in the pool. fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { let bonded_balance = @@ -868,6 +899,7 @@ pub mod pallet { #[pallet::storage] pub type BondedPools = CountedStorageMap<_, Twox64Concat, T::AccountId, BondedPoolStorage>; + // CountedStorageMap<_, Twox64Concat, T::AccountId, BondedPoolStorage>; /// Reward pools. This is where there rewards for each pool accumulate. When a delegators payout /// is claimed, the balance comes out fo the reward pool. Keyed by the bonded pools @@ -1298,7 +1330,6 @@ pub mod pallet { pub fn create( origin: OriginFor, amount: BalanceOf, - index: u16, root: T::AccountId, nominator: T::AccountId, state_toggler: T::AccountId, @@ -1314,52 +1345,46 @@ pub mod pallet { } ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); - let (pool_account, reward_account) = Self::create_accounts(index); - ensure!(!BondedPools::::contains_key(&pool_account), Error::::IdInUse); - - let mut bonded_pool = BondedPool:: { - account: pool_account.clone(), - points: Zero::zero(), - delegator_counter: Zero::zero(), - depositor: who.clone(), - root, - nominator, - state_toggler, - state: PoolState::Open, - }; + let mut bonded_pool = BondedPool::::new(who.clone(), root, nominator, state_toggler); + ensure!(!BondedPools::::contains_key(&bonded_pool.account), Error::::IdInUse); // Increase the delegator counter to account for depositor; checks delegator limits. bonded_pool.inc_delegators()?; // We must calculate the points issued *before* we bond who's funds, else // points:balance ratio will be wrong. let points_issued = bonded_pool.issue(amount); - T::Currency::transfer(&who, &pool_account, amount, ExistenceRequirement::AllowDeath)?; + T::Currency::transfer( + &who, + &bonded_pool.account, + amount, + ExistenceRequirement::AllowDeath, + )?; T::StakingInterface::bond( - pool_account.clone(), + bonded_pool.account.clone(), // We make the stash and controller the same for simplicity - pool_account.clone(), + bonded_pool.account.clone(), amount, - reward_account.clone(), + bonded_pool.reward_account(), )?; Delegators::::insert( who, Delegator:: { - pool: pool_account.clone(), + pool: bonded_pool.account.clone(), points: points_issued, reward_pool_total_earnings: Zero::zero(), unbonding_era: None, }, ); - bonded_pool.put(); RewardPools::::insert( - pool_account, + bonded_pool.account.clone(), RewardPool:: { balance: Zero::zero(), points: U256::zero(), total_earnings: Zero::zero(), - account: reward_account, + account: bonded_pool.reward_account(), }, ); + bonded_pool.put(); // TODO: event @@ -1414,29 +1439,6 @@ pub mod pallet { } impl Pallet { - fn create_accounts(index: u16) -> (T::AccountId, T::AccountId) { - let parent_hash = frame_system::Pallet::::parent_hash(); - // let parent_hash = frame_system::Pallet::::block_number(); // TODO - let ext_index = frame_system::Pallet::::extrinsic_index().unwrap_or_default(); - - // TODO use a transparent prefix before the hashed part - // (b"identifier/stash", (identifier, who).using_encoded(blake2_256)) - // (b"identifier/rewards", (identifier, who).using_encoded(blake2_256)) - // (b"nom-pools/bonded", (index, parent_hash, ext_index).using_encoded(blake2_256)) - // (b"nom-pools/rewards", (index, parent_hash, ext_index).using_encoded(blake2_256)) - let stash_entropy = - (b"pools/stash", index, parent_hash, ext_index).using_encoded(blake2_256); - let reward_entropy = - (b"pools/rewards", index, parent_hash, ext_index).using_encoded(blake2_256); - - ( - Decode::decode(&mut TrailingZeroInput::new(stash_entropy.as_ref())) - .expect("infinite length input; no invalid inputs for type; qed"), - Decode::decode(&mut TrailingZeroInput::new(reward_entropy.as_ref())) - .expect("infinite length input; no invalid inputs for type; qed"), - ) - } - /// Calculate the rewards for `delegator`. fn calculate_delegator_payout( bonded_pool: &BondedPool, From 8084e3a66f73085cd433cedffe524b0e896d5c40 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 2 Mar 2022 18:34:27 -0800 Subject: [PATCH 147/299] Adapt tests to new account id format --- frame/nomination-pools/src/lib.rs | 41 ++++++++++++++++------------- frame/nomination-pools/src/mock.rs | 5 ++-- frame/nomination-pools/src/tests.rs | 21 +++++++-------- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index a01d9452604c9..26c629e246ffd 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -315,10 +315,11 @@ use frame_support::{ traits::{ Currency, DefensiveOption, DefensiveResult, DefensiveSaturating, ExistenceRequirement, Get, }, - DefaultNoBound, PalletId, RuntimeDebugNoBound, + DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_core::U256; +use sp_io::hashing::blake2_256; use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZeroInput, Zero}; use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div, vec::Vec}; @@ -350,8 +351,9 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), + (true, true) | (false, true) => { + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) + }, (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -376,7 +378,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero() + return Zero::zero(); } // Equivalent of (current_balance / current_points) * delegator_points @@ -516,11 +518,10 @@ impl BondedPool { } fn create_account(index: [u8; 4], depositor: T::AccountId) -> T::AccountId { - (b"npls", index, depositor.clone()).using_encoded(|encoded| { - let trimmed = &encoded[..T::AccountId::max_encoded_len()]; - Decode::decode(&mut TrailingZeroInput::new(trimmed)) - .expect("Input is trimmed to the max encoded length. qed") - }) + // TODO: look into make the prefix transparent by not hashing anything + let entropy = (b"npls", index, depositor).using_encoded(blake2_256); + Decode::decode(&mut TrailingZeroInput::new(&entropy)) + .expect("Input is trimmed to the max encoded length. qed") } fn reward_account(&self) -> T::AccountId { @@ -570,8 +571,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) < - BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) + < BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -773,7 +774,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self + return self; } // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -1221,8 +1222,8 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); ensure!( - current_era.saturating_sub(unbonding_era) >= - T::StakingInterface::bonding_duration(), + current_era.saturating_sub(unbonding_era) + >= T::StakingInterface::bonding_duration(), Error::::NotUnbondedYet ); @@ -1336,8 +1337,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() && - amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() + && amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1346,6 +1347,8 @@ pub mod pallet { ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); let mut bonded_pool = BondedPool::::new(who.clone(), root, nominator, state_toggler); + // This shouldn't be possible since we are ensured the delegator is not a depositor and the + // the account ID is generated based on the accountId ensure!(!BondedPools::::contains_key(&bonded_pool.account), Error::::IdInUse); // Increase the delegator counter to account for depositor; checks delegator limits. bonded_pool.inc_delegators()?; @@ -1474,9 +1477,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() || - current_points.is_zero() || - reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() + || current_points.is_zero() + || reward_pool.balance.is_zero() { Zero::zero() } else { diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 0abda4afd2a46..174aef7707fc8 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -7,9 +7,9 @@ pub type AccountId = u32; pub type Balance = u128; /// _Stash_ of the pool that gets created by the [`ExtBuilder`]. -pub const PRIMARY_ACCOUNT: u32 = 1708226889; +pub const PRIMARY_ACCOUNT: u32 = 1552898353; /// Reward destination of the pool that gets created by the [`ExtBuilder`]. -pub const REWARDS_ACCOUNT: u32 = 1842460259; +pub const REWARDS_ACCOUNT: u32 = 3802151463; parameter_types! { pub static CurrentEra: EraIndex = 0; @@ -213,7 +213,6 @@ impl ExtBuilder { assert_ok!(Pools::create( RawOrigin::Signed(10).into(), amount_to_bond, - 0, 900, 901, 902 diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 483c5a2785f3d..18ed1cbcf34f3 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -520,7 +520,7 @@ mod join { assert_eq!(Delegators::::count(), 3); assert_eq!(MaxDelegators::::get(), Some(4)); Balances::make_free_balance_be(&104, 100 + Balances::minimum_balance()); - assert_ok!(Pools::create(Origin::signed(104), 100, 1, 104, 104, 104)); + assert_ok!(Pools::create(Origin::signed(104), 100, 104, 104, 104)); let pool_account = BondedPools::::iter() .find(|(_, bonded_pool)| bonded_pool.depositor == 104) .map(|(pool_account, _)| pool_account) @@ -1989,7 +1989,7 @@ mod create { #[test] fn create_works() { ExtBuilder::default().build_and_execute(|| { - let stash = 337692978; + let stash = 3548237456; assert!(!BondedPools::::contains_key(1)); assert!(!RewardPools::::contains_key(1)); @@ -2000,7 +2000,6 @@ mod create { assert_ok!(Pools::create( Origin::signed(11), StakingMock::minimum_bond(), - 42, 123, 456, 789 @@ -2036,7 +2035,7 @@ mod create { balance: Zero::zero(), points: U256::zero(), total_earnings: Zero::zero(), - account: 2844952004 + account: 1657614948 } ); }); @@ -2046,8 +2045,8 @@ mod create { fn create_errors_correctly() { ExtBuilder::default().build_and_execute_no_checks(|| { assert_noop!( - Pools::create(Origin::signed(11), 420, 0, 123, 456, 789), - Error::::IdInUse + Pools::create(Origin::signed(10), 10, 123, 456, 789), + Error::::AccountBelongsToOtherPool ); // Given @@ -2056,7 +2055,7 @@ mod create { // Then assert_noop!( - Pools::create(Origin::signed(11), 9, 42, 123, 456, 789), + Pools::create(Origin::signed(11), 9, 123, 456, 789), Error::::MinimumBondNotMet ); @@ -2065,7 +2064,7 @@ mod create { // Then assert_noop!( - Pools::create(Origin::signed(11), 19, 42, 123, 456, 789), + Pools::create(Origin::signed(11), 19, 123, 456, 789), Error::::MinimumBondNotMet ); @@ -2086,7 +2085,7 @@ mod create { // Then assert_noop!( - Pools::create(Origin::signed(11), 20, 42, 123, 456, 789), + Pools::create(Origin::signed(11), 20, 123, 456, 789), Error::::MaxPools ); @@ -2097,8 +2096,8 @@ mod create { // Then assert_noop!( - Pools::create(Origin::signed(11), 20, 0, 11, 11, 11), - Error::::MaxPools + Pools::create(Origin::signed(11), 20, 11, 11, 11), + Error::::MaxDelegators ); }); } From f53c82525c5c7b22b4fbc5b311432429f1a7f23d Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 2 Mar 2022 19:26:41 -0800 Subject: [PATCH 148/299] Simplify create_accounts api --- frame/nomination-pools/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 26c629e246ffd..91e1832ec7f25 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -340,8 +340,8 @@ pub type SubPoolsWithEra = BoundedBTreeMap, TotalUnbo type RewardPoints = U256; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; -const BONDED_ACCOUNT_INDEX: [u8; 4] = *b"bond"; -const REWARD_ACCOUNT_INDEX: [u8; 4] = *b"rewd"; +const BONDED_ACCOUNT_INDEX: &[u8; 4] = b"bond"; +const REWARD_ACCOUNT_INDEX: &[u8; 4] = b"rewd"; /// Calculate the number of points to issue from a pool as `(current_points / current_balance) * /// new_funds` except for some zero edge cases; see logic and tests for details. @@ -517,8 +517,9 @@ impl BondedPool { BondedPools::::remove(self.account); } - fn create_account(index: [u8; 4], depositor: T::AccountId) -> T::AccountId { + fn create_account(index: &[u8; 4], depositor: T::AccountId) -> T::AccountId { // TODO: look into make the prefix transparent by not hashing anything + // TODO: look into a using a configurable module id. let entropy = (b"npls", index, depositor).using_encoded(blake2_256); Decode::decode(&mut TrailingZeroInput::new(&entropy)) .expect("Input is trimmed to the max encoded length. qed") From 283e06ab0488c09922f91afc7765424875c124d3 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 3 Mar 2022 17:25:47 -0800 Subject: [PATCH 149/299] Fix staking tests --- Cargo.lock | 625 ++++++++---------- frame/nomination-pools/Cargo.toml | 4 +- .../nomination-pools/benchmarking/Cargo.toml | 6 +- .../nomination-pools/benchmarking/src/lib.rs | 4 +- .../nomination-pools/benchmarking/src/mock.rs | 1 + frame/nomination-pools/src/lib.rs | 43 +- frame/nomination-pools/src/mock.rs | 8 +- frame/staking/src/mock.rs | 17 +- 8 files changed, 310 insertions(+), 398 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 960a06255a244..beb2789b5931a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -485,7 +485,7 @@ dependencies = [ "fnv", "futures 0.3.19", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "sc-chain-spec", "sc-client-api", @@ -520,7 +520,7 @@ dependencies = [ "jsonrpc-derive", "jsonrpc-pubsub", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "sc-rpc", "sc-utils", @@ -549,8 +549,8 @@ version = "4.0.0-dev" dependencies = [ "hex", "hex-literal", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-application-crypto", "sp-core", @@ -600,28 +600,16 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitvec" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848" -dependencies = [ - "funty 1.1.0", - "radium 0.6.2", - "tap", - "wyz 0.2.0", -] - [[package]] name = "bitvec" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" dependencies = [ - "funty 2.0.0", - "radium 0.7.0", + "funty", + "radium", "tap", - "wyz 0.5.0", + "wyz", ] [[package]] @@ -2032,10 +2020,10 @@ dependencies = [ "futures-timer", "log 0.4.14", "num-traits", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.11.2", "rand 0.8.4", - "scale-info 2.0.1", + "scale-info", ] [[package]] @@ -2094,7 +2082,7 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" name = "fork-tree" version = "3.0.0" dependencies = [ - "parity-scale-codec 3.0.0", + "parity-scale-codec", ] [[package]] @@ -2116,9 +2104,9 @@ dependencies = [ "hex-literal", "linregress", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "paste 1.0.6", - "scale-info 2.0.1", + "scale-info", "serde", "sp-api", "sp-application-crypto", @@ -2147,7 +2135,7 @@ dependencies = [ "linked-hash-map", "log 0.4.14", "memory-db", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "rand 0.8.4", "sc-cli", "sc-client-api", @@ -2176,8 +2164,8 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-arithmetic", "sp-core", "sp-io", @@ -2195,8 +2183,8 @@ dependencies = [ "hex-literal", "pallet-balances", "pallet-transaction-payment", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-inherents", "sp-io", @@ -2213,8 +2201,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" dependencies = [ "cfg-if 1.0.0", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", ] @@ -2230,11 +2218,11 @@ dependencies = [ "impl-trait-for-tuples", "log 0.4.14", "once_cell", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parity-util-mem", "paste 1.0.6", "pretty_assertions", - "scale-info 2.0.1", + "scale-info", "serde", "smallvec 1.8.0", "sp-arithmetic", @@ -2288,10 +2276,10 @@ dependencies = [ "frame-support", "frame-support-test-pallet", "frame-system", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "pretty_assertions", "rustversion", - "scale-info 2.0.1", + "scale-info", "serde", "sp-arithmetic", "sp-core", @@ -2309,8 +2297,8 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-runtime", "sp-version", @@ -2322,8 +2310,8 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", ] [[package]] @@ -2333,8 +2321,8 @@ dependencies = [ "criterion", "frame-support", "log 0.4.14", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-externalities", @@ -2352,8 +2340,8 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -2364,7 +2352,7 @@ dependencies = [ name = "frame-system-rpc-runtime-api" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-api", ] @@ -2428,12 +2416,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -[[package]] -name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - [[package]] name = "funty" version = "2.0.0" @@ -3088,7 +3070,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" dependencies = [ - "parity-scale-codec 3.0.0", + "parity-scale-codec", ] [[package]] @@ -4827,7 +4809,7 @@ dependencies = [ "pallet-im-online", "pallet-timestamp", "pallet-transaction-payment", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "platforms", "rand 0.8.4", "regex", @@ -4900,9 +4882,9 @@ dependencies = [ "pallet-im-online", "pallet-timestamp", "pallet-treasury", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-executor", - "scale-info 2.0.1", + "scale-info", "sp-application-crypto", "sp-consensus-babe", "sp-core", @@ -4921,7 +4903,7 @@ name = "node-inspect" version = "0.9.0-dev" dependencies = [ "clap 3.0.7", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-cli", "sc-client-api", "sc-executor", @@ -4937,8 +4919,8 @@ name = "node-primitives" version = "2.0.0" dependencies = [ "frame-system", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-application-crypto", "sp-core", "sp-runtime", @@ -5041,8 +5023,8 @@ dependencies = [ "pallet-utility", "pallet-vesting", "pallet-whitelist", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-authority-discovery", "sp-block-builder", @@ -5131,8 +5113,8 @@ dependencies = [ "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-block-builder", "sp-consensus-aura", @@ -5160,7 +5142,7 @@ dependencies = [ "node-runtime", "pallet-asset-tx-payment", "pallet-transaction-payment", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-block-builder", "sc-client-api", "sc-client-db", @@ -5434,8 +5416,8 @@ dependencies = [ "pallet-authorship", "pallet-balances", "pallet-transaction-payment", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "serde_json", "smallvec 1.8.0", @@ -5454,8 +5436,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5469,8 +5451,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5484,8 +5466,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-timestamp", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-application-crypto", "sp-consensus-aura", "sp-core", @@ -5501,8 +5483,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-session", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-application-crypto", "sp-authority-discovery", "sp-core", @@ -5518,8 +5500,8 @@ dependencies = [ "frame-support", "frame-system", "impl-trait-for-tuples", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-authorship", "sp-core", "sp-io", @@ -5543,8 +5525,8 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-application-crypto", "sp-consensus-babe", "sp-consensus-vrf", @@ -5566,8 +5548,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5613,8 +5595,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-transaction-payment", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5629,8 +5611,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-session", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -5655,8 +5637,8 @@ dependencies = [ "pallet-mmr", "pallet-mmr-primitives", "pallet-session", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -5675,8 +5657,8 @@ dependencies = [ "log 0.4.14", "pallet-balances", "pallet-treasury", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5694,8 +5676,8 @@ dependencies = [ "pallet-balances", "pallet-bounties", "pallet-treasury", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5710,8 +5692,8 @@ dependencies = [ "frame-support", "frame-system", "log 0.4.14", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5736,11 +5718,11 @@ dependencies = [ "pallet-randomness-collective-flip", "pallet-timestamp", "pallet-utility", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "pretty_assertions", "rand 0.8.4", "rand_pcg 0.3.1", - "scale-info 2.0.1", + "scale-info", "serde", "smallvec 1.8.0", "sp-core", @@ -5759,8 +5741,8 @@ name = "pallet-contracts-primitives" version = "6.0.0" dependencies = [ "bitflags", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-rpc", @@ -5786,7 +5768,7 @@ dependencies = [ "jsonrpc-derive", "pallet-contracts-primitives", "pallet-contracts-rpc-runtime-api", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "serde", "serde_json", "sp-api", @@ -5801,8 +5783,8 @@ name = "pallet-contracts-rpc-runtime-api" version = "4.0.0-dev" dependencies = [ "pallet-contracts-primitives", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-runtime", "sp-std", @@ -5818,8 +5800,8 @@ dependencies = [ "frame-system", "pallet-balances", "pallet-scheduler", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -5836,8 +5818,8 @@ dependencies = [ "frame-system", "pallet-balances", "pallet-scheduler", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -5855,10 +5837,10 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "rand 0.7.3", - "scale-info 2.0.1", + "scale-info", "sp-arithmetic", "sp-core", "sp-io", @@ -5879,8 +5861,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-npos-elections", @@ -5898,8 +5880,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5914,8 +5896,8 @@ dependencies = [ "frame-system", "lite-json", "log 0.4.14", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-keystore", @@ -5929,8 +5911,8 @@ version = "3.0.0-dev" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5946,8 +5928,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-arithmetic", "sp-core", "sp-io", @@ -5972,8 +5954,8 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-application-crypto", "sp-core", "sp-finality-grandpa", @@ -5994,8 +5976,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6012,8 +5994,8 @@ dependencies = [ "log 0.4.14", "pallet-authorship", "pallet-session", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-application-crypto", "sp-core", "sp-io", @@ -6030,8 +6012,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-keyring", @@ -6048,8 +6030,8 @@ dependencies = [ "frame-support-test", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6064,8 +6046,8 @@ dependencies = [ "frame-support", "frame-system", "log 0.4.14", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6083,8 +6065,8 @@ dependencies = [ "frame-system", "hex-literal", "pallet-mmr-primitives", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6099,7 +6081,7 @@ dependencies = [ "frame-system", "hex-literal", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "serde", "sp-api", "sp-core", @@ -6115,7 +6097,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "pallet-mmr-primitives", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "serde", "serde_json", "sp-api", @@ -6132,8 +6114,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6147,8 +6129,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6162,8 +6144,8 @@ dependencies = [ "frame-support", "frame-system", "log 0.4.14", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6177,8 +6159,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 2.3.1", - "scale-info 1.0.0", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6202,8 +6184,8 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec 2.3.1", - "scale-info 1.0.0", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6219,8 +6201,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -6246,8 +6228,8 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6263,8 +6245,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6280,8 +6262,8 @@ dependencies = [ "frame-system", "pallet-balances", "pallet-utility", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6294,9 +6276,9 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "safe-mix", - "scale-info 2.0.1", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6310,8 +6292,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6329,8 +6311,8 @@ dependencies = [ "pallet-balances", "pallet-preimage", "pallet-scheduler", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -6347,8 +6329,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-preimage", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6363,8 +6345,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6380,8 +6362,8 @@ dependencies = [ "impl-trait-for-tuples", "log 0.4.14", "pallet-timestamp", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6404,9 +6386,9 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "rand 0.7.3", - "scale-info 2.0.1", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6422,9 +6404,9 @@ dependencies = [ "frame-support-test", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "rand_chacha 0.2.2", - "scale-info 2.0.1", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6446,9 +6428,9 @@ dependencies = [ "pallet-session", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "rand_chacha 0.2.2", - "scale-info 2.0.1", + "scale-info", "serde", "sp-application-crypto", "sp-core", @@ -6486,8 +6468,8 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6501,8 +6483,8 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6516,8 +6498,8 @@ dependencies = [ "frame-support", "frame-system", "log 0.4.14", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-inherents", "sp-io", @@ -6536,8 +6518,8 @@ dependencies = [ "log 0.4.14", "pallet-balances", "pallet-treasury", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -6553,8 +6535,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "serde_json", "smallvec 1.8.0", @@ -6572,7 +6554,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "pallet-transaction-payment-rpc-runtime-api", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-api", "sp-blockchain", "sp-core", @@ -6585,7 +6567,7 @@ name = "pallet-transaction-payment-rpc-runtime-api" version = "4.0.0-dev" dependencies = [ "pallet-transaction-payment", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-api", "sp-runtime", ] @@ -6599,8 +6581,8 @@ dependencies = [ "frame-system", "hex-literal", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-inherents", @@ -6619,8 +6601,8 @@ dependencies = [ "frame-system", "impl-trait-for-tuples", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -6637,8 +6619,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6653,8 +6635,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6670,8 +6652,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6687,8 +6669,8 @@ dependencies = [ "frame-system", "pallet-balances", "pallet-preimage", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-core", "sp-io", @@ -6715,20 +6697,6 @@ dependencies = [ "snap", ] -[[package]] -name = "parity-scale-codec" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909" -dependencies = [ - "arrayvec 0.7.1", - "bitvec 0.20.4", - "byte-slice-cast", - "impl-trait-for-tuples", - "parity-scale-codec-derive 2.3.1", - "serde", -] - [[package]] name = "parity-scale-codec" version = "3.0.0" @@ -6736,25 +6704,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a7f3fcf5e45fc28b84dcdab6b983e77f197ec01f325a33f404ba6855afd1070" dependencies = [ "arrayvec 0.7.1", - "bitvec 1.0.0", + "bitvec", "byte-slice-cast", "impl-trait-for-tuples", - "parity-scale-codec-derive 3.0.0", + "parity-scale-codec-derive", "serde", ] -[[package]] -name = "parity-scale-codec-derive" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" -dependencies = [ - "proc-macro-crate 1.1.0", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "parity-scale-codec-derive" version = "3.0.0" @@ -7240,7 +7196,7 @@ dependencies = [ "fixed-hash", "impl-codec", "impl-serde", - "scale-info 2.0.1", + "scale-info", "uint", ] @@ -7421,12 +7377,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" - [[package]] name = "radium" version = "0.7.0" @@ -7804,7 +7754,7 @@ dependencies = [ "jsonrpsee 0.8.0", "log 0.4.14", "pallet-elections-phragmen", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "serde", "serde_json", "sp-core", @@ -8058,7 +8008,7 @@ dependencies = [ "ip_network", "libp2p", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "prost", "prost-build", "quickcheck", @@ -8084,7 +8034,7 @@ dependencies = [ "futures 0.3.19", "futures-timer", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", "sc-client-api", @@ -8106,7 +8056,7 @@ dependencies = [ name = "sc-block-builder" version = "0.10.0-dev" dependencies = [ - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-client-api", "sp-api", "sp-block-builder", @@ -8124,7 +8074,7 @@ version = "4.0.0-dev" dependencies = [ "impl-trait-for-tuples", "memmap2 0.5.0", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-chain-spec-derive", "sc-network", "sc-telemetry", @@ -8156,7 +8106,7 @@ dependencies = [ "libp2p", "log 0.4.14", "names", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "rand 0.7.3", "regex", "rpassword", @@ -8190,7 +8140,7 @@ dependencies = [ "futures 0.3.19", "hash-db", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "sc-executor", "sc-transaction-pool-api", @@ -8223,7 +8173,7 @@ dependencies = [ "linked-hash-map", "log 0.4.14", "parity-db", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "quickcheck", "sc-client-api", @@ -8272,7 +8222,7 @@ dependencies = [ "futures 0.3.19", "getrandom 0.2.3", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", "sc-client-api", @@ -8314,7 +8264,7 @@ dependencies = [ "num-bigint", "num-rational 0.2.4", "num-traits", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "rand 0.7.3", "rand_chacha 0.2.2", @@ -8386,7 +8336,7 @@ name = "sc-consensus-epochs" version = "0.10.0-dev" dependencies = [ "fork-tree", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-client-api", "sc-consensus", "sp-blockchain", @@ -8404,7 +8354,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-basic-authorship", "sc-client-api", "sc-consensus", @@ -8440,7 +8390,7 @@ dependencies = [ "futures 0.3.19", "futures-timer", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "sc-client-api", "sc-consensus", @@ -8464,7 +8414,7 @@ dependencies = [ "futures 0.3.19", "futures-timer", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-client-api", "sc-consensus", "sc-telemetry", @@ -8498,7 +8448,7 @@ dependencies = [ "hex-literal", "lazy_static", "lru 0.6.6", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "paste 1.0.6", "regex", @@ -8533,7 +8483,7 @@ name = "sc-executor-common" version = "0.10.0-dev" dependencies = [ "environmental", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-allocator", "sp-core", "sp-maybe-compressed-blob", @@ -8551,7 +8501,7 @@ name = "sc-executor-wasmi" version = "0.10.0-dev" dependencies = [ "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-allocator", "sc-executor-common", "scoped-tls", @@ -8568,7 +8518,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parity-wasm 0.42.2", "sc-allocator", "sc-executor-common", @@ -8594,7 +8544,7 @@ dependencies = [ "futures 0.3.19", "futures-timer", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "rand 0.8.4", "sc-block-builder", @@ -8637,7 +8587,7 @@ dependencies = [ "jsonrpc-derive", "jsonrpc-pubsub", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-block-builder", "sc-client-api", "sc-finality-grandpa", @@ -8707,7 +8657,7 @@ dependencies = [ "linked_hash_set", "log 0.4.14", "lru 0.7.0", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "pin-project 1.0.10", "prost", @@ -8800,7 +8750,7 @@ dependencies = [ "lazy_static", "num_cpus", "once_cell", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "rand 0.7.3", "sc-block-builder", @@ -8854,7 +8804,7 @@ dependencies = [ "jsonrpc-pubsub", "lazy_static", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", "sc-chain-spec", @@ -8890,7 +8840,7 @@ dependencies = [ "jsonrpc-derive", "jsonrpc-pubsub", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "sc-chain-spec", "sc-transaction-pool-api", @@ -8948,7 +8898,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-pubsub", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", "pin-project 1.0.10", @@ -9009,7 +8959,7 @@ dependencies = [ "hex", "hex-literal", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", "sc-client-api", @@ -9041,7 +8991,7 @@ name = "sc-state-db" version = "0.10.0-dev" dependencies = [ "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parity-util-mem", "parity-util-mem-derive", "parking_lot 0.12.0", @@ -9056,7 +9006,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-chain-spec", "sc-client-api", "sc-consensus-babe", @@ -9138,7 +9088,7 @@ dependencies = [ "hex", "linked-hash-map", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", "retain_mut", @@ -9186,45 +9136,20 @@ dependencies = [ "tokio-test", ] -[[package]] -name = "scale-info" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55b744399c25532d63a0d2789b109df8d46fc93752d46b0782991a931a782f" -dependencies = [ - "bitvec 0.20.4", - "cfg-if 1.0.0", - "derive_more", - "parity-scale-codec 2.3.1", - "scale-info-derive 1.0.0", -] - [[package]] name = "scale-info" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0563970d79bcbf3c537ce3ad36d859b30d36fc5b190efd227f1f7a84d7cf0d42" dependencies = [ - "bitvec 1.0.0", + "bitvec", "cfg-if 1.0.0", "derive_more", - "parity-scale-codec 3.0.0", - "scale-info-derive 2.0.0", + "parity-scale-codec", + "scale-info-derive", "serde", ] -[[package]] -name = "scale-info-derive" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baeb2780690380592f86205aa4ee49815feb2acad8c2f59e6dd207148c3f1fcd" -dependencies = [ - "proc-macro-crate 1.1.0", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "scale-info-derive" version = "2.0.0" @@ -9708,7 +9633,7 @@ version = "4.0.0-dev" dependencies = [ "hash-db", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-api-proc-macro", "sp-core", "sp-runtime", @@ -9737,7 +9662,7 @@ dependencies = [ "criterion", "futures 0.3.19", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "rustversion", "sc-block-builder", "sp-api", @@ -9755,8 +9680,8 @@ dependencies = [ name = "sp-application-crypto" version = "6.0.0" dependencies = [ - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -9782,10 +9707,10 @@ dependencies = [ "criterion", "integer-sqrt", "num-traits", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "primitive-types", "rand 0.7.3", - "scale-info 2.0.1", + "scale-info", "serde", "sp-debug-derive", "sp-std", @@ -9806,8 +9731,8 @@ dependencies = [ name = "sp-authority-discovery" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-application-crypto", "sp-runtime", @@ -9819,7 +9744,7 @@ name = "sp-authorship" version = "4.0.0-dev" dependencies = [ "async-trait", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-inherents", "sp-runtime", "sp-std", @@ -9829,7 +9754,7 @@ dependencies = [ name = "sp-block-builder" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-api", "sp-inherents", "sp-runtime", @@ -9843,7 +9768,7 @@ dependencies = [ "futures 0.3.19", "log 0.4.14", "lru 0.7.0", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "sp-api", "sp-consensus", @@ -9861,7 +9786,7 @@ dependencies = [ "futures 0.3.19", "futures-timer", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-core", "sp-inherents", "sp-runtime", @@ -9877,8 +9802,8 @@ name = "sp-consensus-aura" version = "0.10.0-dev" dependencies = [ "async-trait", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-application-crypto", "sp-consensus", @@ -9895,8 +9820,8 @@ version = "0.10.0-dev" dependencies = [ "async-trait", "merlin", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-api", "sp-application-crypto", @@ -9915,7 +9840,7 @@ dependencies = [ name = "sp-consensus-pow" version = "0.10.0-dev" dependencies = [ - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-api", "sp-core", "sp-runtime", @@ -9926,8 +9851,8 @@ dependencies = [ name = "sp-consensus-slots" version = "0.10.0-dev" dependencies = [ - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-arithmetic", "sp-runtime", @@ -9939,7 +9864,7 @@ dependencies = [ name = "sp-consensus-vrf" version = "0.10.0-dev" dependencies = [ - "parity-scale-codec 3.0.0", + "parity-scale-codec", "schnorrkel", "sp-core", "sp-runtime", @@ -9968,13 +9893,13 @@ dependencies = [ "log 0.4.14", "merlin", "num-traits", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", "primitive-types", "rand 0.7.3", "regex", - "scale-info 2.0.1", + "scale-info", "schnorrkel", "secp256k1", "secrecy", @@ -10041,7 +9966,7 @@ name = "sp-externalities" version = "0.12.0" dependencies = [ "environmental", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-std", "sp-storage", ] @@ -10052,8 +9977,8 @@ version = "4.0.0-dev" dependencies = [ "finality-grandpa", "log 0.4.14", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "serde", "sp-api", "sp-application-crypto", @@ -10070,7 +9995,7 @@ dependencies = [ "async-trait", "futures 0.3.19", "impl-trait-for-tuples", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-core", "sp-runtime", "sp-std", @@ -10085,7 +10010,7 @@ dependencies = [ "hash-db", "libsecp256k1", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "secp256k1", "sp-core", @@ -10118,7 +10043,7 @@ dependencies = [ "async-trait", "futures 0.3.19", "merlin", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "rand 0.7.3", "rand_chacha 0.2.2", @@ -10141,9 +10066,9 @@ dependencies = [ name = "sp-npos-elections" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.0.0", + "parity-scale-codec", "rand 0.7.3", - "scale-info 2.0.1", + "scale-info", "serde", "sp-arithmetic", "sp-core", @@ -10159,9 +10084,9 @@ version = "2.0.0-alpha.5" dependencies = [ "clap 3.0.7", "honggfuzz", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "rand 0.8.4", - "scale-info 2.0.1", + "scale-info", "sp-npos-elections", "sp-runtime", ] @@ -10170,11 +10095,11 @@ dependencies = [ name = "sp-npos-elections-solution-type" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.0.0", + "parity-scale-codec", "proc-macro-crate 1.1.0", "proc-macro2", "quote", - "scale-info 2.0.1", + "scale-info", "sp-arithmetic", "sp-npos-elections", "syn", @@ -10217,11 +10142,11 @@ dependencies = [ "hash256-std-hasher", "impl-trait-for-tuples", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parity-util-mem", "paste 1.0.6", "rand 0.7.3", - "scale-info 2.0.1", + "scale-info", "serde", "serde_json", "sp-api", @@ -10241,7 +10166,7 @@ name = "sp-runtime-interface" version = "6.0.0" dependencies = [ "impl-trait-for-tuples", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "primitive-types", "rustversion", "sp-core", @@ -10313,7 +10238,7 @@ version = "0.10.0-dev" dependencies = [ "assert_matches", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-core", "sp-io", "sp-std", @@ -10334,8 +10259,8 @@ dependencies = [ name = "sp-session" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-core", "sp-runtime", @@ -10347,8 +10272,8 @@ dependencies = [ name = "sp-staking" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-runtime", "sp-std", ] @@ -10361,7 +10286,7 @@ dependencies = [ "hex-literal", "log 0.4.14", "num-traits", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "pretty_assertions", "rand 0.7.3", @@ -10387,7 +10312,7 @@ name = "sp-storage" version = "6.0.0" dependencies = [ "impl-serde", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "ref-cast", "serde", "sp-debug-derive", @@ -10399,7 +10324,7 @@ name = "sp-tasks" version = "4.0.0-dev" dependencies = [ "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-core", "sp-externalities", "sp-io", @@ -10411,7 +10336,7 @@ dependencies = [ name = "sp-test-primitives" version = "2.0.0" dependencies = [ - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parity-util-mem", "serde", "sp-application-crypto", @@ -10426,7 +10351,7 @@ dependencies = [ "async-trait", "futures-timer", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-api", "sp-inherents", "sp-runtime", @@ -10438,7 +10363,7 @@ dependencies = [ name = "sp-tracing" version = "5.0.0" dependencies = [ - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-std", "tracing", "tracing-core", @@ -10459,8 +10384,8 @@ version = "4.0.0-dev" dependencies = [ "async-trait", "log 0.4.14", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-inherents", "sp-runtime", @@ -10476,8 +10401,8 @@ dependencies = [ "hash-db", "hex-literal", "memory-db", - "parity-scale-codec 3.0.0", - "scale-info 2.0.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-runtime", "sp-std", @@ -10493,9 +10418,9 @@ name = "sp-version" version = "5.0.0" dependencies = [ "impl-serde", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parity-wasm 0.42.2", - "scale-info 2.0.1", + "scale-info", "serde", "sp-core-hashing-proc-macro", "sp-runtime", @@ -10508,7 +10433,7 @@ dependencies = [ name = "sp-version-proc-macro" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.0.0", + "parity-scale-codec", "proc-macro2", "quote", "sp-version", @@ -10521,7 +10446,7 @@ version = "6.0.0" dependencies = [ "impl-trait-for-tuples", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sp-std", "wasmi", "wasmtime", @@ -10658,9 +10583,9 @@ dependencies = [ "frame-system", "futures 0.3.19", "jsonrpc-client-transports", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-rpc-api", - "scale-info 2.0.1", + "scale-info", "serde", "sp-storage", "tokio", @@ -10676,7 +10601,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-client-api", "sc-rpc-api", "sc-transaction-pool", @@ -10709,7 +10634,7 @@ dependencies = [ "async-trait", "futures 0.3.19", "hex", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-client-api", "sc-client-db", "sc-consensus", @@ -10740,12 +10665,12 @@ dependencies = [ "memory-db", "pallet-babe", "pallet-timestamp", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parity-util-mem", "sc-block-builder", "sc-executor", "sc-service", - "scale-info 2.0.1", + "scale-info", "serde", "sp-api", "sp-application-crypto", @@ -10778,7 +10703,7 @@ name = "substrate-test-runtime-client" version = "2.0.0" dependencies = [ "futures 0.3.19", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "sc-block-builder", "sc-client-api", "sc-consensus", @@ -10796,7 +10721,7 @@ name = "substrate-test-runtime-transaction-pool" version = "2.0.0" dependencies = [ "futures 0.3.19", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "parking_lot 0.12.0", "sc-transaction-pool", "sc-transaction-pool-api", @@ -11359,7 +11284,7 @@ dependencies = [ "hash-db", "keccak-hasher", "memory-db", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "trie-db", "trie-root", "trie-standardmap", @@ -11453,7 +11378,7 @@ dependencies = [ "clap 3.0.7", "jsonrpsee 0.4.1", "log 0.4.14", - "parity-scale-codec 3.0.0", + "parity-scale-codec", "remote-externalities", "sc-chain-spec", "sc-cli", @@ -12465,12 +12390,6 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "wyz" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" - [[package]] name = "wyz" version = "0.5.0" diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 741ac23cefb42..153b988facee9 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -13,8 +13,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # parity -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } # FRAME frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index 01cac625920bc..f6ead8c9af2ad 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -13,9 +13,9 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -# Parity -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +# parity +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } # FRAME frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 86c09555ae4b3..8d7f729625973 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -12,7 +12,7 @@ use frame_support::{ensure, traits::Get}; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ BalanceOf, BondedPoolStorage, BondedPools, Delegators, MinCreateBond, MinJoinBond, - Pallet as Pools, PoolState, RewardPools, SubPoolsStorage, UnbondPool, + Pallet as Pools, PoolState, RewardPools, SubPoolsStorage, }; use sp_runtime::traits::{StaticLookup, Zero}; use sp_staking::{EraIndex, StakingInterface}; @@ -60,7 +60,6 @@ fn create_pool_account( Pools::::create( Origin::Signed(pool_creator.clone()).into(), balance, - n as u16, pool_creator.clone(), pool_creator.clone(), pool_creator.clone(), @@ -466,7 +465,6 @@ frame_benchmarking::benchmarks! { }: _( Origin::Signed(depositor.clone()), min_create_bond, - 0, depositor.clone(), depositor.clone(), depositor.clone() diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index e19bc8a792740..cb20ed87b2925 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -108,6 +108,7 @@ impl pallet_staking::Config for Runtime { frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; type GenesisElectionProvider = Self::ElectionProvider; type SortedListProvider = pallet_bags_list::Pallet; + type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 5a1ffa2f6a5d6..4cee8b9f8d67d 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -310,7 +310,7 @@ use frame_support::{ ensure, - pallet_prelude::*, + pallet_prelude::{MaxEncodedLen, *}, storage::bounded_btree_map::BoundedBTreeMap, traits::{ Currency, DefensiveOption, DefensiveResult, DefensiveSaturating, ExistenceRequirement, Get, @@ -323,7 +323,6 @@ use sp_io::hashing::blake2_256; use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZeroInput, Zero}; use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div, vec::Vec}; - #[cfg(test)] mod mock; #[cfg(test)] @@ -351,9 +350,8 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => { - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) - }, + (true, true) | (false, true) => + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -378,7 +376,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero(); + return Zero::zero() } // Equivalent of (current_balance / current_points) * delegator_points @@ -438,9 +436,7 @@ pub struct BondedPoolStorage { } #[derive(RuntimeDebugNoBound)] -// #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] -// #[cfg_attr(feature = "std", derive(Clone,))] pub struct BondedPool { /// Points of the pool. Each delegator has some corresponding the points. The portion of points /// that belong to a delegator represent the portion of the pools bonded funds belong to the @@ -483,6 +479,10 @@ impl BondedPool { delegator_counter: Zero::zero(), } } + + // TODO: figure out if we should ditch BondedPoolStorage vs BondedPool and instead just have + // BondedPool that doesn't have `account` field. Instead just use deterministic accountId + // generation function. Only downside is this will have some increased computational cost /// Get [`Self`] from storage. Returns `None` if no entry for `pool_account` exists. fn get(pool_account: &T::AccountId) -> Option { BondedPools::::try_get(pool_account).ok().map(|storage| Self { @@ -521,8 +521,7 @@ impl BondedPool { // TODO: look into make the prefix transparent by not hashing anything // TODO: look into a using a configurable module id. let entropy = (b"npls", index, depositor).using_encoded(blake2_256); - Decode::decode(&mut TrailingZeroInput::new(&entropy)) - .expect("Infinite length input. qed") + Decode::decode(&mut TrailingZeroInput::new(&entropy)).expect("Infinite length input. qed") } fn reward_account(&self) -> T::AccountId { @@ -572,8 +571,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) - < BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) < + BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -775,7 +774,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self; + return self } // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -1223,8 +1222,8 @@ pub mod pallet { let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); ensure!( - current_era.saturating_sub(unbonding_era) - >= T::StakingInterface::bonding_duration(), + current_era.saturating_sub(unbonding_era) >= + T::StakingInterface::bonding_duration(), Error::::NotUnbondedYet ); @@ -1338,8 +1337,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() - && amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() && + amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1348,8 +1347,8 @@ pub mod pallet { ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); let mut bonded_pool = BondedPool::::new(who.clone(), root, nominator, state_toggler); - // This shouldn't be possible since we are ensured the delegator is not a depositor and the - // the account ID is generated based on the accountId + // This shouldn't be possible since we are ensured the delegator is not a depositor and + // the the account ID is generated based on the accountId ensure!(!BondedPools::::contains_key(&bonded_pool.account), Error::::IdInUse); // Increase the delegator counter to account for depositor; checks delegator limits. bonded_pool.inc_delegators()?; @@ -1478,9 +1477,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() - || current_points.is_zero() - || reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() || + current_points.is_zero() || + reward_pool.balance.is_zero() { Zero::zero() } else { diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 174aef7707fc8..1fcbbe7897833 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -210,13 +210,7 @@ impl ExtBuilder { // make a pool let amount_to_bond = ::StakingInterface::minimum_bond(); Balances::make_free_balance_be(&10, amount_to_bond * 2); - assert_ok!(Pools::create( - RawOrigin::Signed(10).into(), - amount_to_bond, - 900, - 901, - 902 - )); + assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902)); for (account_id, bonded) in self.delegators { Balances::make_free_balance_be(&account_id, bonded * 2); diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index cada09c7d6ef0..a905a3d25101c 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -235,7 +235,7 @@ const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = parameter_types! { pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; pub static MaxNominations: u32 = 16; - pub static LedgerSlashPerEra: (BalanceOf, BTreeMap) = (Zero::zero(), BTreeMap::new()); + pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); } impl pallet_bags_list::Config for Test { @@ -251,13 +251,14 @@ impl onchain::Config for Test { } struct OnStakerSlashMock(core::marker::PhantomData); -impl sp_staking::OnStakerSlash> for OnStakerSlashMock { +impl sp_staking::OnStakerSlash< +AccountId, Balance> for OnStakerSlashMock { fn on_slash( - pool_account: &T::AccountId, - slashed_bonded: BalanceOf, - slashed_chunks: &BTreeMap>, + _pool_account: &AccountId, + slashed_bonded: Balance, + slashed_chunks: &BTreeMap, ) { - LedgerSlashPerEra::set((slashed_bonded as u128, slashed_chunks.clone())); + LedgerSlashPerEra::set((slashed_bonded, slashed_chunks.clone())); } } @@ -787,9 +788,9 @@ pub(crate) fn on_offence_in_era( for &(bonded_era, start_session) in bonded_eras.iter() { if bonded_era == era { let _ = Staking::on_offence(offenders, slash_fraction, start_session, disable_strategy); - return; + return } else if bonded_era > era { - break; + break } } From 94f6c527d923397e342d808ccf47d1285dca2555 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 3 Mar 2022 20:00:48 -0800 Subject: [PATCH 150/299] Save PerBill slash impl before removing --- frame/staking/src/lib.rs | 46 +++++++++++++++++++++-------------- frame/staking/src/mock.rs | 3 +-- frame/staking/src/slashing.rs | 10 ++++---- frame/staking/src/tests.rs | 16 +++++++++--- 4 files changed, 47 insertions(+), 28 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 56e7df7fb008b..3e1468b77cb8f 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -555,11 +555,15 @@ impl StakingLedger { use sp_runtime::traits::CheckedMul as _; use sp_staking::OnStakerSlash as _; use sp_std::ops::Div as _; + if slash_amount.is_zero() { + return Zero::zero() + } let mut remaining_slash = slash_amount; let pre_slash_total = self.total; // The index of the first chunk after the slash era + // TODO: we want slash_era + 1? Double check why though let start_index = self.unlocking.partition_point(|c| c.era < slash_era); // The indices of from the first chunk after the slash up through the most recent chunk. // (The most recent chunk is at greatest from this era) @@ -578,29 +582,35 @@ impl StakingLedger { self.active.saturating_add(unbonding_affected_balance) }; - if affected_balance.is_zero() { - // Exit early because there is nothing to slash - return Zero::zero() - } - // Wether or not this slash can be applied to just the active and affected unbonding chunks. - // If not, we have to slash all of the aforementioned and then continue slashing older - // unlocking chunks. - let is_proportional_slash = slash_amount < affected_balance; + // Its ok if this ratio is 1, that means the affected balance is less than the + // `slash_amount`, in which case we just try and slash as much as possible + let ratio = if affected_balance.is_zero() { + Perbill::one() + } else { + let ratio = Perbill::from_rational(slash_amount, affected_balance); + if ratio.is_zero() { + // We know that slash_amount isn't zero, so from_rational was not able to + // approximate with high enough fidelity. + Perbill::one() + } else { + ratio + } + }; // Helper to update `target` and the ledgers total after accounting for slashing `target`. let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { // We don't want the added complexity of using extended ints, so if this saturates we // will always just try and slash as much as possible. - let maybe_numerator = slash_amount.checked_mul(target); // Use a Perbill - println!("{:?}=maybe_numerator", maybe_numerator); - - // Calculate the amount to slash from the target - let slash_from_target = match (maybe_numerator, is_proportional_slash) { - // Perbill::from_rational(slash_amount, affected_balance) * target - // Equivalent to `(slash_amount / affected_balance) * target`. - (Some(numerator), true) => numerator.div(affected_balance), - (None, _) | (_, false) => (*slash_remaining).min(*target), - }; + + let slash_from_target = ratio.mul_floor(*target).min(*slash_remaining); + + // let maybe_numerator = slash_amount.checked_mul(target); + // // // Calculate the amount to slash from the target + // let slash_from_target = match (maybe_numerator, is_proportional_slash) { + // // Equivalent to `(slash_amount / affected_balance) * target`. + // (Some(numerator), true) => numerator.div(affected_balance), + // (None, _) | (_, false) => (*slash_remaining).min(*target), + // }; *target = target.saturating_sub(slash_from_target); diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index a905a3d25101c..d7e6e05b53ab7 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -251,8 +251,7 @@ impl onchain::Config for Test { } struct OnStakerSlashMock(core::marker::PhantomData); -impl sp_staking::OnStakerSlash< -AccountId, Balance> for OnStakerSlashMock { +impl sp_staking::OnStakerSlash for OnStakerSlashMock { fn on_slash( _pool_account: &AccountId, slashed_bonded: Balance, diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 773d7ca9ddb16..edf46d464471d 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -124,7 +124,7 @@ impl SlashingSpans { pub(crate) fn end_span(&mut self, now: EraIndex) -> bool { let next_start = now + 1; if next_start <= self.last_start { - return false + return false; } let last_length = next_start - self.last_start; @@ -236,7 +236,7 @@ pub(crate) fn compute_slash( // kick out the validator even if they won't be slashed, // as long as the misbehavior is from their most recent slashing span. kick_out_if_recent::(params); - return None + return None; } let (prior_slash_p, _era_slash) = @@ -259,7 +259,7 @@ pub(crate) fn compute_slash( // pays out some reward even if the latest report is not max-in-era. // we opt to avoid the nominator lookups and edits and leave more rewards // for more drastic misbehavior. - return None + return None; } // apply slash to validator. @@ -542,7 +542,7 @@ impl<'a, T: 'a + Config> Drop for InspectingSpans<'a, T> { fn drop(&mut self) { // only update on disk if we slashed this account. if !self.dirty { - return + return; } if let Some((start, end)) = self.spans.prune(self.window_start) { @@ -671,7 +671,7 @@ fn pay_reporters( // nobody to pay out to or nothing to pay; // just treat the whole value as slashed. T::Slash::on_unbalanced(slashed_imbalance); - return + return; } // take rewards out of the slashed imbalance. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 3e999409ff664..866893e732e9f 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4814,13 +4814,23 @@ fn ledger_slash_works() { * get swept */ 0 ), - 475 + 450 ); // Then assert_eq!(ledger.active, 500 / 2); - assert_eq!(ledger.unlocking, vec![c(0, 0), c(1, 100 / 2), c(2, 0), c(3, 250 / 2)]); - assert_eq!(ledger.total, 425); + assert_eq!( + ledger.unlocking, + vec![ + c(0, 0), + c(1, 100 / 2), + c(2, 0), + // We reach the slash amount early due to 2 of the previous chunks falling below ED + // and getting swept + c(3, 250 / 2 + 25) + ] + ); + assert_eq!(ledger.total, 450); // Given we have the same as above, ledger.unlocking = bounded_vec![c(0, 40), c(1, 100), c(2, 10), c(3, 250)]; From d37c6e068d3846e5b92398fe162ac07fb64dc8a6 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 3 Mar 2022 20:06:01 -0800 Subject: [PATCH 151/299] Rever ledger slash test --- frame/staking/src/lib.rs | 43 +++++++++++--------------------------- frame/staking/src/tests.rs | 8 +++---- 2 files changed, 15 insertions(+), 36 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 3e1468b77cb8f..7649a52987ea4 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -523,7 +523,7 @@ impl StakingLedger { } if unlocking_balance >= value { - break + break; } } @@ -556,7 +556,7 @@ impl StakingLedger { use sp_staking::OnStakerSlash as _; use sp_std::ops::Div as _; if slash_amount.is_zero() { - return Zero::zero() + return Zero::zero(); } let mut remaining_slash = slash_amount; @@ -582,35 +582,16 @@ impl StakingLedger { self.active.saturating_add(unbonding_affected_balance) }; - // Its ok if this ratio is 1, that means the affected balance is less than the - // `slash_amount`, in which case we just try and slash as much as possible - let ratio = if affected_balance.is_zero() { - Perbill::one() - } else { - let ratio = Perbill::from_rational(slash_amount, affected_balance); - if ratio.is_zero() { - // We know that slash_amount isn't zero, so from_rational was not able to - // approximate with high enough fidelity. - Perbill::one() - } else { - ratio - } - }; - + let is_proportional_slash = slash_amount < affected_balance; // Helper to update `target` and the ledgers total after accounting for slashing `target`. let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { - // We don't want the added complexity of using extended ints, so if this saturates we - // will always just try and slash as much as possible. - - let slash_from_target = ratio.mul_floor(*target).min(*slash_remaining); - - // let maybe_numerator = slash_amount.checked_mul(target); - // // // Calculate the amount to slash from the target - // let slash_from_target = match (maybe_numerator, is_proportional_slash) { - // // Equivalent to `(slash_amount / affected_balance) * target`. - // (Some(numerator), true) => numerator.div(affected_balance), - // (None, _) | (_, false) => (*slash_remaining).min(*target), - // }; + let maybe_numerator = slash_amount.checked_mul(target); + // // Calculate the amount to slash from the target + let slash_from_target = match (maybe_numerator, is_proportional_slash) { + // Equivalent to `(slash_amount / affected_balance) * target`. + (Some(numerator), true) => numerator.div(affected_balance), + (None, _) | (_, false) => (*slash_remaining).min(*target), + }; *target = target.saturating_sub(slash_from_target); @@ -640,10 +621,10 @@ impl StakingLedger { slashed_unlocking.insert(chunk.era, chunk.value); if remaining_slash.is_zero() { - break + break; } } else { - break // defensive, indices should always be in bounds. + break; // defensive, indices should always be in bounds. } } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 866893e732e9f..bf245e569c23e 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4814,7 +4814,7 @@ fn ledger_slash_works() { * get swept */ 0 ), - 450 + 475 ); // Then @@ -4825,12 +4825,10 @@ fn ledger_slash_works() { c(0, 0), c(1, 100 / 2), c(2, 0), - // We reach the slash amount early due to 2 of the previous chunks falling below ED - // and getting swept - c(3, 250 / 2 + 25) + c(3, 250 / 2) ] ); - assert_eq!(ledger.total, 450); + assert_eq!(ledger.total, 425); // Given we have the same as above, ledger.unlocking = bounded_vec![c(0, 40), c(1, 100), c(2, 10), c(3, 250)]; From 7c5ff10b76169ed66c74555ee5c5559754ca1364 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 3 Mar 2022 20:29:02 -0800 Subject: [PATCH 152/299] Get node runtime to work --- bin/node/runtime/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 66540b1aa3afd..1b7c1d5426e51 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -559,6 +559,7 @@ impl pallet_staking::Config for Runtime { // Note that the aforementioned does not scale to a very large number of nominators. type SortedListProvider = BagsList; type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = NominationPools; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; } From 16635065f19f34d451024424b3685dba48d783cd Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Mar 2022 12:48:08 -0800 Subject: [PATCH 153/299] Organize sub pools by unbond era, not curren era --- frame/nomination-pools/src/lib.rs | 36 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 4cee8b9f8d67d..7482a45382c8b 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -350,8 +350,9 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), + (true, true) | (false, true) => { + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) + }, (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -376,7 +377,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero() + return Zero::zero(); } // Equivalent of (current_balance / current_points) * delegator_points @@ -571,8 +572,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) < - BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) + < BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -774,7 +775,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self + return self; } // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -1140,6 +1141,7 @@ pub mod pallet { // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPoolsStorage::::get(&delegator.pool).unwrap_or_default(); let current_era = T::StakingInterface::current_era(); + let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); let balance_to_unbond = bonded_pool.balance_to_unbond(delegator.points); @@ -1152,14 +1154,14 @@ pub mod pallet { // Merge any older pools into the general, era agnostic unbond pool. Note that we do // this before inserting to ensure we don't go over the max unbonding pools. - let mut sub_pools = sub_pools.maybe_merge_pools(current_era); + let mut sub_pools = sub_pools.maybe_merge_pools(unbond_era); // Update the unbond pool associated with the current era with the // unbonded funds. Note that we lazily create the unbond pool if it // does not yet exist. - sub_pools.unchecked_with_era_get_or_make(current_era).issue(balance_to_unbond); + sub_pools.unchecked_with_era_get_or_make(unbond_era).issue(balance_to_unbond); - delegator.unbonding_era = Some(current_era); + delegator.unbonding_era = Some(unbond_era); Self::deposit_event(Event::::Unbonded { delegator: target.clone(), @@ -1221,11 +1223,7 @@ pub mod pallet { let delegator = Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); - ensure!( - current_era.saturating_sub(unbonding_era) >= - T::StakingInterface::bonding_duration(), - Error::::NotUnbondedYet - ); + ensure!(current_era >= unbonding_era, Error::::NotUnbondedYet); let mut sub_pools = SubPoolsStorage::::get(&delegator.pool) .defensive_ok_or_else(|| Error::::SubPoolsNotFound)?; @@ -1337,8 +1335,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() && - amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() + && amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1477,9 +1475,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() || - current_points.is_zero() || - reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() + || current_points.is_zero() + || reward_pool.balance.is_zero() { Zero::zero() } else { From 9642ad4ce73189c79c30dfc78be1cc673c772dc3 Mon Sep 17 00:00:00 2001 From: Zeke Mostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Mar 2022 13:25:12 -0800 Subject: [PATCH 154/299] staking: Proportional ledger slashing --- frame/staking/src/lib.rs | 171 +++++++++++++------- frame/staking/src/mock.rs | 13 ++ frame/staking/src/pallet/impls.rs | 9 +- frame/staking/src/pallet/mod.rs | 7 +- frame/staking/src/slashing.rs | 17 +- frame/staking/src/tests.rs | 249 ++++++++++++++++++++++++++++++ primitives/staking/src/lib.rs | 25 +++ 7 files changed, 426 insertions(+), 65 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index d833ac86fe0bd..9958461d92145 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -440,31 +440,30 @@ pub struct UnlockChunk { /// The ledger of a (bonded) stash. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct StakingLedger { +#[scale_info(skip_type_params(T))] +pub struct StakingLedger { /// The stash account whose balance is actually locked and at stake. - pub stash: AccountId, + pub stash: T::AccountId, /// The total amount of the stash's balance that we are currently accounting for. /// It's just `active` plus all the `unlocking` balances. #[codec(compact)] - pub total: Balance, + pub total: BalanceOf, /// The total amount of the stash's balance that will be at stake in any forthcoming /// rounds. #[codec(compact)] - pub active: Balance, + pub active: BalanceOf, /// Any balance that is becoming free, which may eventually be transferred out of the stash /// (assuming it doesn't get slashed first). It is assumed that this will be treated as a first /// in, first out queue where the new (higher value) eras get pushed on the back. - pub unlocking: BoundedVec, MaxUnlockingChunks>, + pub unlocking: BoundedVec>, MaxUnlockingChunks>, /// List of eras for which the stakers behind a validator have claimed rewards. Only updated /// for validators. pub claimed_rewards: Vec, } -impl - StakingLedger -{ +impl StakingLedger { /// Initializes the default object using the given `validator`. - pub fn default_from(stash: AccountId) -> Self { + pub fn default_from(stash: T::AccountId) -> Self { Self { stash, total: Zero::zero(), @@ -507,8 +506,8 @@ impl (Self, Balance) { - let mut unlocking_balance: Balance = Zero::zero(); + fn rebond(mut self, value: BalanceOf) -> (Self, BalanceOf) { + let mut unlocking_balance = BalanceOf::::zero(); while let Some(last) = self.unlocking.last_mut() { if unlocking_balance + last.value <= value { @@ -530,57 +529,121 @@ impl StakingLedger -where - Balance: AtLeast32BitUnsigned + Saturating + Copy, -{ - /// Slash the validator for a given amount of balance. This can grow the value - /// of the slash in the case that the validator has less than `minimum_balance` - /// active funds. Returns the amount of funds actually slashed. + /// Slash the staker for a given amount of balance. This can grow the value + /// of the slash in the case that either the active bonded or some unlocking chunks become dust + /// after slashing. Returns the amount of funds actually slashed. /// - /// Slashes from `active` funds first, and then `unlocking`, starting with the - /// chunks that are closest to unlocking. - fn slash(&mut self, mut value: Balance, minimum_balance: Balance) -> Balance { - let pre_total = self.total; - let total = &mut self.total; - let active = &mut self.active; - - let slash_out_of = - |total_remaining: &mut Balance, target: &mut Balance, value: &mut Balance| { - let mut slash_from_target = (*value).min(*target); - - if !slash_from_target.is_zero() { - *target -= slash_from_target; - - // Don't leave a dust balance in the staking system. - if *target <= minimum_balance { - slash_from_target += *target; - *value += sp_std::mem::replace(target, Zero::zero()); - } + /// Note that this calls `Config::OnStakerSlash::on_slash` with information as to how the slash + /// was applied. + /// + /// Generally slashes are computed by: + /// + /// 1) Balances of the unlocking chunks in range `slash_era + 1..=apply_era` are summed and + /// stored in `total_balance_affected`. + /// + /// 2) `slash_ratio` is computed as `slash_amount / total_balance_affected`. + /// + /// 3) `Ledger::active` is set to `(1- slash_ratio) * Ledger::active`. + /// + /// 4) For all unlocking chunks created in range `slash_era + 1..=apply_era` set their balance + /// to `(1 - slash_ratio) * unbonding_pool_balance`. We start with slash_era + 1, because that + /// is the earliest the active sake while slash could be unbonded. + /// + /// 5) Slash any remaining slash amount from the remaining chunks, starting with the `slash_era` + /// and going backwards. + /// + /// There are some edge cases due to saturating arithmetic - see logic below and tests for + /// details. + fn slash( + &mut self, + slash_amount: BalanceOf, + minimum_balance: BalanceOf, + slash_era: EraIndex, + ) -> BalanceOf { + use sp_runtime::traits::CheckedMul as _; + use sp_staking::OnStakerSlash as _; + use sp_std::ops::Div as _; + if slash_amount.is_zero() { + return Zero::zero() + } - *total_remaining = total_remaining.saturating_sub(slash_from_target); - *value -= slash_from_target; - } + let mut remaining_slash = slash_amount; + let pre_slash_total = self.total; + + // The index of the first chunk after the slash era + let era_after_slash = slash_era + 1; + // When a user unbonds, the chunk is given the era `current_era + BondingDuration`. See + // logic in [`Self::unbond`]. + let era_of_chunks_created_after_slash = era_after_slash + T::BondingDuration::get(); + let start_index = + self.unlocking.partition_point(|c| c.era < era_of_chunks_created_after_slash); + // The indices of from the first chunk after the slash up through the most recent chunk. + // (The most recent chunk is at greatest from this era) + let affected_indices = start_index..self.unlocking.len(); + + // Calculate the total balance of active funds and unlocking funds in the affected range. + let affected_balance = { + let unbonding_affected_balance = + affected_indices.clone().fold(BalanceOf::::zero(), |sum, i| { + if let Some(chunk) = self.unlocking.get_mut(i) { + sum.saturating_add(chunk.value) + } else { + sum + } + }); + self.active.saturating_add(unbonding_affected_balance) + }; + + let is_proportional_slash = slash_amount < affected_balance; + // Helper to update `target` and the ledgers total after accounting for slashing `target`. + let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { + let maybe_numerator = slash_amount.checked_mul(target); + // // Calculate the amount to slash from the target + let slash_from_target = match (maybe_numerator, is_proportional_slash) { + // Equivalent to `(slash_amount / affected_balance) * target`. + (Some(numerator), true) => numerator.div(affected_balance), + (None, _) | (_, false) => (*target).min(*slash_remaining), }; - slash_out_of(total, active, &mut value); + *target = *target - slash_from_target; // slash_from_target cannot be gt target. - let i = self - .unlocking - .iter_mut() - .map(|chunk| { - slash_out_of(total, &mut chunk.value, &mut value); - chunk.value - }) - .take_while(|value| value.is_zero()) // Take all fully-consumed chunks out. - .count(); + let actual_slashed = if *target <= minimum_balance { + // Slash the rest of the target if its dust + sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) + } else { + slash_from_target + }; + + self.total = self.total.saturating_sub(actual_slashed); + *slash_remaining = slash_remaining.saturating_sub(actual_slashed); + }; + + // If this is *not* a proportional slash, the active will always wiped to 0. + slash_out_of(&mut self.active, &mut remaining_slash); + + let mut slashed_unlocking = BTreeMap::<_, _>::new(); + let indices_to_slash + // First slash unbonding chunks from after the slash + = affected_indices + // Then start slashing older chunks, start from the era before the slash + .chain((0..start_index).rev()); + for i in indices_to_slash { + if let Some(chunk) = self.unlocking.get_mut(i) { + slash_out_of(&mut chunk.value, &mut remaining_slash); + slashed_unlocking.insert(chunk.era, chunk.value); + + if remaining_slash.is_zero() { + break + } + } else { + break // defensive, indices should always be in bounds. + } + } - // Kill all drained chunks. - let _ = self.unlocking.drain(..i); + T::OnStakerSlash::on_slash(&self.stash, self.active, &slashed_unlocking); - pre_total.saturating_sub(*total) + pre_slash_total.saturating_sub(self.total) } } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 12d3804b4e303..fa7524f5b3dcc 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -235,6 +235,7 @@ const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = parameter_types! { pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; pub static MaxNominations: u32 = 16; + pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); } impl pallet_bags_list::Config for Test { @@ -249,6 +250,17 @@ impl onchain::Config for Test { type DataProvider = Staking; } +pub struct OnStakerSlashMock(core::marker::PhantomData); +impl sp_staking::OnStakerSlash for OnStakerSlashMock { + fn on_slash( + _pool_account: &AccountId, + slashed_bonded: Balance, + slashed_chunks: &BTreeMap, + ) { + LedgerSlashPerEra::set((slashed_bonded, slashed_chunks.clone())); + } +} + impl crate::pallet::pallet::Config for Test { type MaxNominations = MaxNominations; type Currency = Balances; @@ -272,6 +284,7 @@ impl crate::pallet::pallet::Config for Test { // NOTE: consider a macro and use `UseNominatorsMap` as well. type SortedListProvider = BagsList; type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = OnStakerSlashMock; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 5cd0d0107f015..b91eab8f6c3e1 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -201,10 +201,7 @@ impl Pallet { /// Update the ledger for a controller. /// /// This will also update the stash lock. - pub(crate) fn update_ledger( - controller: &T::AccountId, - ledger: &StakingLedger>, - ) { + pub(crate) fn update_ledger(controller: &T::AccountId, ledger: &StakingLedger) { T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); >::insert(controller, ledger); } @@ -594,7 +591,7 @@ impl Pallet { for era in (*earliest)..keep_from { let era_slashes = ::UnappliedSlashes::take(&era); for slash in era_slashes { - slashing::apply_slash::(slash); + slashing::apply_slash::(slash, era); } } @@ -1219,7 +1216,7 @@ where unapplied.reporters = details.reporters.clone(); if slash_defer_duration == 0 { // Apply right away. - slashing::apply_slash::(unapplied); + slashing::apply_slash::(unapplied, slash_era); { let slash_cost = (6, 5); let reward_cost = (2, 2); diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 58f9fd237263b..dcdde1a8f279d 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -173,6 +173,10 @@ pub mod pallet { #[pallet::constant] type MaxUnlockingChunks: Get; + /// A hook called when any staker is slashed. Mostly likely this can be a no-op unless + /// there are delegation pools. + type OnStakerSlash: sp_staking::OnStakerSlash>; + /// Some parameters of the benchmarking. type BenchmarkingConfig: BenchmarkingConfig; @@ -235,8 +239,7 @@ pub mod pallet { /// Map from all (unlocked) "controller" accounts to the info regarding the staking. #[pallet::storage] #[pallet::getter(fn ledger)] - pub type Ledger = - StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>>; + pub type Ledger = StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>; /// Where the reward payment should be made. Keyed by stash. #[pallet::storage] diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 2f381ad631fe5..9fc50eaf538f6 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -598,6 +598,7 @@ pub fn do_slash( value: BalanceOf, reward_payout: &mut BalanceOf, slashed_imbalance: &mut NegativeImbalanceOf, + slash_era: EraIndex, ) { let controller = match >::bonded(stash) { None => return, // defensive: should always exist. @@ -609,7 +610,7 @@ pub fn do_slash( None => return, // nothing to do. }; - let value = ledger.slash(value, T::Currency::minimum_balance()); + let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era); if !value.is_zero() { let (imbalance, missing) = T::Currency::slash(stash, value); @@ -628,7 +629,10 @@ pub fn do_slash( } /// Apply a previously-unapplied slash. -pub(crate) fn apply_slash(unapplied_slash: UnappliedSlash>) { +pub(crate) fn apply_slash( + unapplied_slash: UnappliedSlash>, + slash_era: EraIndex, +) { let mut slashed_imbalance = NegativeImbalanceOf::::zero(); let mut reward_payout = unapplied_slash.payout; @@ -637,10 +641,17 @@ pub(crate) fn apply_slash(unapplied_slash: UnappliedSlash(&nominator, nominator_slash, &mut reward_payout, &mut slashed_imbalance); + do_slash::( + &nominator, + nominator_slash, + &mut reward_payout, + &mut slashed_imbalance, + slash_era, + ); } pay_reporters::(reward_payout, slashed_imbalance, &unapplied_slash.reporters); diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 2b2a32e0edab7..70f754056d9da 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4801,3 +4801,252 @@ fn force_apply_min_commission_works() { ); }); } + +#[test] +fn ledger_slash_works() { + let c = |era, value| UnlockChunk:: { era, value }; + // Given + let mut ledger = StakingLedger:: { + stash: 123, + total: 10, + active: 10, + unlocking: bounded_vec![], + claimed_rewards: vec![], + }; + + assert_eq!(BondingDuration::get(), 3); + + // When we slash a ledger with no unlocking chunks + assert_eq!(ledger.slash(5, 1, 0), 5); + // Then + assert_eq!(ledger.total, 5); + assert_eq!(ledger.active, 5); + assert_eq!(LedgerSlashPerEra::get().0, 5); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); + + // When we slash a ledger with no unlocking chunks and the slash amount is greater then the + // total + assert_eq!(ledger.slash(11, 1, 0), 5); + // Then + assert_eq!(ledger.total, 0); + assert_eq!(ledger.active, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); + + // Given + ledger.unlocking = bounded_vec![c(4, 10), c(5, 10)]; + ledger.total = 2 * 10; + ledger.active = 0; + // When all the chunks overlap with the slash eras + assert_eq!(ledger.slash(20, 0, 0), 20); + // Then + assert_eq!(ledger.unlocking, vec![c(4, 0), c(5, 0)]); + assert_eq!(ledger.total, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)])); + + // Given + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + + // When we have a partial slash that touches all chunks + assert_eq!(ledger.slash(900 / 2, 0, 0), 450); + + // Then + assert_eq!(ledger.active, 500 / 2); + assert_eq!(ledger.unlocking, vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)]); + assert_eq!(ledger.total, 900 / 2); + assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)]) + ); + + // Given we have the same as above, + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + assert_eq!(ledger.total, 900); + + // When we have a higher min balance + assert_eq!( + ledger.slash( + 900 / 2, + 25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it to + * get swept */ + 0 + ), + 475 + ); + + // Then + assert_eq!(ledger.active, 500 / 2); + assert_eq!(ledger.unlocking, vec![c(4, 0), c(5, 100 / 2), c(6, 0), c(7, 250 / 2)]); + assert_eq!(ledger.total, 425); + assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 250 / 2)]) + ); + + // Given we have the same as above, + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + + // When + assert_eq!( + ledger.slash( + 900 / 2, + 25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it to + * get swept */ + 0 + ), + 475 + ); + + // Then + assert_eq!(ledger.active, 500 / 2); + assert_eq!(ledger.unlocking, vec![c(4, 0), c(5, 100 / 2), c(6, 0), c(7, 250 / 2)]); + assert_eq!(ledger.total, 425); + assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 250 / 2)]) + ); + + // Given + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + + // When + assert_eq!( + ledger.slash( + 500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2 + 0, + 2 /* slash era 2+4 first, so the affected parts are era 2+4, era 3+4 and + * ledge.active. This will cause the affected to go to zero, and then we will + * start slashing older chunks */ + ), + 500 + 250 + 10 + 100 / 2 + ); + + assert_eq!(ledger.active, 0); + // iteration order ------------------NA----------2-------------0--------1---- + assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2), c(6, 0), c(7, 0)]); + assert_eq!(ledger.total, 90); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)])); + + // Given + ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; + ledger.active = 100; + ledger.total = 5 * 100; + + // When + assert_eq!( + ledger.slash( + 350, // active + era 2 + era 3 + era 1 / 2 + 50, + 2 /* slash era 2+4 first, so the affected parts are era 2+4, era 3+4 and + * ledge.active. This will cause the affected to go to zero, and then we will + * start slashing older chunks */ + ), + 400 + ); + + // Then + assert_eq!(ledger.active, 0); + // iteration order ------------------NA---------2--------0--------1---- + assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 0), c(6, 0), c(7, 0)]); + //------goes to min balance and then gets dusted^^^ + assert_eq!(ledger.total, 100); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)])); + + // Tests for saturating arithmetic + + // Given + let slash = u64::MAX as Balance * 2; + let value = slash + - (9 * 4) // The value of the other parts of ledger that will get slashed + + 1; + // slash * value will saturate + assert!(slash.checked_mul(value - 20).is_none()); + + ledger.active = 10; + ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)]; + + ledger.total = value + 40; + + // When + assert_eq!(ledger.slash(slash, 0, 0), slash); + + // Then + assert_eq!(ledger.active, 1); // slash of 9 + assert_eq!( + ledger.unlocking, + vec![ + c(4, 1), // slash of 9 + c(5, 1), // slash of 9 + c(6, 1), // slash of 9 ------------(slash - 9 * 4).min(value) + c(7, 1), // saturates, so slash of remaining_slash.min(value) + ] + ); + assert_eq!(ledger.total, 5); + assert_eq!(LedgerSlashPerEra::get().0, 1); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 1), (5, 1), (6, 1), (7, 1)])); + + // Given + let slash = u64::MAX as Balance * 2; + let value = u64::MAX as Balance * 2; + let unit = 100; + // slash * value that will saturate + assert!(slash.checked_mul(value).is_none()); + // but slash * unit. + assert!(slash.checked_mul(unit).is_some()); + + ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)]; + //--------------------------------------note value^^^ + ledger.active = unit; + ledger.total = unit * 4 + value; + + // When + assert_eq!(ledger.slash(slash, 0, 0), slash); + + // Then + + // The amount slashed out of `unit` + let unit_slash = { + let affected_balance = value + unit * 4; + slash * unit / affected_balance + }; + // `unit` after the slash is applied + let unit_slashed = unit - unit_slash; + + // `value` after the slash is applied + let value_slashed = { + // We slash active and era 1 before we slash era 2 + let previous_slashed_amount = unit_slash * 2; + let remaining_slash = slash - previous_slashed_amount; + value - remaining_slash + }; + assert_eq!(ledger.active, unit_slashed); + assert_eq!( + ledger.unlocking, + vec![ + c(4, unit_slashed), + // We reached the slash here because we slashed value.min(remaining_slash), which was + // remaining_slash + c(5, value_slashed), + c(6, unit), /* The rest are untouched even though they where in the slashing range. + * This is problematic for pools, but should be rare enough that its ok. */ + c(7, unit) + ] + ); + assert_eq!(ledger.total, unit_slashed + unit_slashed + value_slashed + unit + unit); + assert_eq!(LedgerSlashPerEra::get().0, unit_slashed); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, unit_slashed), (5, value_slashed)])); +} diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 15208df62cc66..09eff0717c8e8 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -19,6 +19,7 @@ //! A crate which contains primitives that are useful for implementation that uses staking //! approaches in general. Definitions related to sessions, slashing, etc go here. +use sp_std::collections::btree_map::BTreeMap; pub mod offence; @@ -27,3 +28,27 @@ pub type SessionIndex = u32; /// Counter for the number of eras that have passed. pub type EraIndex = u32; + +/// Trait describing something that implements a hook for any operations to perform when a staker is +/// slashed. +pub trait OnStakerSlash { + /// A hook for any operations to perform when a staker is slashed. + /// + /// # Arguments + /// + /// * `stash` - The stash of the staker whom the slash was applied to. + /// * `slashed_bonded` - The new bonded balance of the staker after the slash was applied. + /// * `slashed_unlocking` - A map from eras that the staker is unbonding in to the new balance + /// after the slash was applied. + fn on_slash( + stash: &AccountId, + slashed_bonded: Balance, + slashed_unlocking: &BTreeMap, + ); +} + +impl OnStakerSlash for () { + fn on_slash(_: &AccountId, _: Balance, _: &BTreeMap) { + // Nothing to do here + } +} From 2a3609d5851040d2cb3c434af631408489e5b329 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Mar 2022 14:14:20 -0800 Subject: [PATCH 155/299] Some comment cleanup --- frame/staking/src/lib.rs | 8 +++--- frame/staking/src/pallet/mod.rs | 2 +- frame/staking/src/tests.rs | 50 +++++++++++---------------------- 3 files changed, 21 insertions(+), 39 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 9958461d92145..11d726ba562dc 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -571,14 +571,13 @@ impl StakingLedger { let mut remaining_slash = slash_amount; let pre_slash_total = self.total; - // The index of the first chunk after the slash era let era_after_slash = slash_era + 1; // When a user unbonds, the chunk is given the era `current_era + BondingDuration`. See // logic in [`Self::unbond`]. let era_of_chunks_created_after_slash = era_after_slash + T::BondingDuration::get(); let start_index = self.unlocking.partition_point(|c| c.era < era_of_chunks_created_after_slash); - // The indices of from the first chunk after the slash up through the most recent chunk. + // The indices of the first chunk after the slash up through the most recent chunk. // (The most recent chunk is at greatest from this era) let affected_indices = start_index..self.unlocking.len(); @@ -599,10 +598,11 @@ impl StakingLedger { // Helper to update `target` and the ledgers total after accounting for slashing `target`. let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { let maybe_numerator = slash_amount.checked_mul(target); - // // Calculate the amount to slash from the target let slash_from_target = match (maybe_numerator, is_proportional_slash) { // Equivalent to `(slash_amount / affected_balance) * target`. (Some(numerator), true) => numerator.div(affected_balance), + // If the slash amount is gt than the affected balance OR the arithmetic to + // calculate the proportion saturated, we just try to slash as much as possible. (None, _) | (_, false) => (*target).min(*slash_remaining), }; @@ -626,7 +626,7 @@ impl StakingLedger { let indices_to_slash // First slash unbonding chunks from after the slash = affected_indices - // Then start slashing older chunks, start from the era before the slash + // Then start slashing older chunks, start from the era of the slash .chain((0..start_index).rev()); for i in indices_to_slash { if let Some(chunk) = self.unlocking.get_mut(i) { diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index dcdde1a8f279d..bc5207d91dda7 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -174,7 +174,7 @@ pub mod pallet { type MaxUnlockingChunks: Get; /// A hook called when any staker is slashed. Mostly likely this can be a no-op unless - /// there are delegation pools. + /// there are nomination pools. type OnStakerSlash: sp_staking::OnStakerSlash>; /// Some parameters of the benchmarking. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 70f754056d9da..c847157c5c120 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4849,10 +4849,8 @@ fn ledger_slash_works() { ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; ledger.active = 500; ledger.total = 40 + 10 + 100 + 250 + 500; // 900 - - // When we have a partial slash that touches all chunks + // When we have a partial slash that touches all chunks assert_eq!(ledger.slash(900 / 2, 0, 0), 450); - // Then assert_eq!(ledger.active, 500 / 2); assert_eq!(ledger.unlocking, vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)]); @@ -4868,7 +4866,6 @@ fn ledger_slash_works() { ledger.active = 500; ledger.total = 40 + 10 + 100 + 250 + 500; // 900 assert_eq!(ledger.total, 900); - // When we have a higher min balance assert_eq!( ledger.slash( @@ -4879,7 +4876,6 @@ fn ledger_slash_works() { ), 475 ); - // Then assert_eq!(ledger.active, 500 / 2); assert_eq!(ledger.unlocking, vec![c(4, 0), c(5, 100 / 2), c(6, 0), c(7, 250 / 2)]); @@ -4894,18 +4890,16 @@ fn ledger_slash_works() { ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; ledger.active = 500; ledger.total = 40 + 10 + 100 + 250 + 500; // 900 - - // When + // When assert_eq!( ledger.slash( 900 / 2, - 25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it to + 25, /* min balance - chunks with era 5 & 5 will be slashed to <=25, causing them to * get swept */ 0 ), 475 ); - // Then assert_eq!(ledger.active, 500 / 2); assert_eq!(ledger.unlocking, vec![c(4, 0), c(5, 100 / 2), c(6, 0), c(7, 250 / 2)]); @@ -4920,8 +4914,7 @@ fn ledger_slash_works() { ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; ledger.active = 500; ledger.total = 40 + 10 + 100 + 250 + 500; // 900 - - // When + // When assert_eq!( ledger.slash( 500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2 @@ -4932,7 +4925,7 @@ fn ledger_slash_works() { ), 500 + 250 + 10 + 100 / 2 ); - + // Then assert_eq!(ledger.active, 0); // iteration order ------------------NA----------2-------------0--------1---- assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2), c(6, 0), c(7, 0)]); @@ -4944,19 +4937,17 @@ fn ledger_slash_works() { ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; ledger.active = 100; ledger.total = 5 * 100; - // When assert_eq!( ledger.slash( - 350, // active + era 2 + era 3 + era 1 / 2 - 50, - 2 /* slash era 2+4 first, so the affected parts are era 2+4, era 3+4 and - * ledge.active. This will cause the affected to go to zero, and then we will - * start slashing older chunks */ + 350, // active + era 6 + era 7 + era 5 / 2 + 50, // min balance - everything slashed to 50 or below will get dusted + 2 /* slash era 2+4 first, so the affected parts are era 2+4, era 3+4 and + * ledge.active. This will cause the affected to go to zero, and then we will + * start slashing older chunks */ ), 400 ); - // Then assert_eq!(ledger.active, 0); // iteration order ------------------NA---------2--------0--------1---- @@ -4975,15 +4966,11 @@ fn ledger_slash_works() { + 1; // slash * value will saturate assert!(slash.checked_mul(value - 20).is_none()); - ledger.active = 10; ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)]; - ledger.total = value + 40; - // When assert_eq!(ledger.slash(slash, 0, 0), slash); - // Then assert_eq!(ledger.active, 1); // slash of 9 assert_eq!( @@ -4991,8 +4978,8 @@ fn ledger_slash_works() { vec![ c(4, 1), // slash of 9 c(5, 1), // slash of 9 - c(6, 1), // slash of 9 ------------(slash - 9 * 4).min(value) - c(7, 1), // saturates, so slash of remaining_slash.min(value) + c(6, 1), // slash of 9 + c(7, 1), // saturates, so slash of remaining_slash.min(value) equivalent of (slash - 9 * 4).min(value) ] ); assert_eq!(ledger.total, 5); @@ -5005,19 +4992,15 @@ fn ledger_slash_works() { let unit = 100; // slash * value that will saturate assert!(slash.checked_mul(value).is_none()); - // but slash * unit. + // but slash * unit won't. assert!(slash.checked_mul(unit).is_some()); - ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)]; //--------------------------------------note value^^^ ledger.active = unit; ledger.total = unit * 4 + value; - // When assert_eq!(ledger.slash(slash, 0, 0), slash); - // Then - // The amount slashed out of `unit` let unit_slash = { let affected_balance = value + unit * 4; @@ -5025,10 +5008,9 @@ fn ledger_slash_works() { }; // `unit` after the slash is applied let unit_slashed = unit - unit_slash; - // `value` after the slash is applied let value_slashed = { - // We slash active and era 1 before we slash era 2 + // We slash active and era 4 before we slash era 5 let previous_slashed_amount = unit_slash * 2; let remaining_slash = slash - previous_slashed_amount; value - remaining_slash @@ -5038,8 +5020,8 @@ fn ledger_slash_works() { ledger.unlocking, vec![ c(4, unit_slashed), - // We reached the slash here because we slashed value.min(remaining_slash), which was - // remaining_slash + // We reached the full slash amount here because we slashed value.min(remaining_slash), + // which was remaining_slash c(5, value_slashed), c(6, unit), /* The rest are untouched even though they where in the slashing range. * This is problematic for pools, but should be rare enough that its ok. */ From 33e6f48c42f75572436106c4b41955d3c1a228a7 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sat, 5 Mar 2022 16:47:53 -0800 Subject: [PATCH 156/299] Add more test post checks --- frame/nomination-pools/src/lib.rs | 30 +++++++++++++----------------- frame/nomination-pools/src/mock.rs | 15 +++++++++++---- frame/staking/src/lib.rs | 8 ++++---- frame/staking/src/slashing.rs | 10 +++++----- frame/staking/src/tests.rs | 10 +--------- 5 files changed, 34 insertions(+), 39 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 7482a45382c8b..59c8b03bd3b46 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -294,16 +294,13 @@ // * If the depositor is actively unbonding, the pool is in destroying state. To achieve this, once // a pool is flipped to a destroying state it cannot change its state. // * The sum of each pools delegator counter equals the `Delegators::count()`. +// * A pool's `delegator_counter` should always be gt 0. // TODO -// - Counter for delegators per pool and allow limiting of delegators per pool -// - tests for the above // - counter for delegators should never be below 1 (below 1 means the pool should be destroyed) // - write detailed docs for StakingInterface -// - various back ports -// - test delegator counter // - slashing - test for correct input to hook (these tests need to be in staking side) -// - transparent account ids - prefix with pls/rewd and pls/stsh, also use PalletId for entropy +// - transparent prefx for account ids // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -350,9 +347,8 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => { - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) - }, + (true, true) | (false, true) => + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -377,7 +373,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero(); + return Zero::zero() } // Equivalent of (current_balance / current_points) * delegator_points @@ -572,8 +568,8 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) - < BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) < + BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -775,7 +771,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self; + return self } // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -1335,8 +1331,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() - && amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() && + amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1475,9 +1471,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() - || current_points.is_zero() - || reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() || + current_points.is_zero() || + reward_pool.balance.is_zero() { Zero::zero() } else { diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 1fcbbe7897833..aab997cabe5e8 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -244,10 +244,17 @@ fn post_checks() { assert_eq!(RewardPools::::count(), BondedPools::::count()); assert!(SubPoolsStorage::::count() <= BondedPools::::count()); assert!(Delegators::::count() >= BondedPools::::count()); - // TODO - // MaxDelegator === Delegator::count - // Each pool has most max delegators per pool - // sum of all pools delegator counts + bonding_pools_checks(); +} + +fn bonding_pools_checks() { + let mut delegators_seen = 0; + for (_account, pool) in BondedPools::::iter() { + assert!(pool.delegator_counter >= 1); + assert!(pool.delegator_counter <= MaxDelegatorsPerPool::::get().unwrap()); + delegators_seen += pool.delegator_counter; + } + assert_eq!(delegators_seen, Delegators::::count()); } pub(crate) fn unsafe_set_state(pool_account: &AccountId, state: PoolState) -> Result<(), ()> { diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 7649a52987ea4..78ea3bdde17e0 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -523,7 +523,7 @@ impl StakingLedger { } if unlocking_balance >= value { - break; + break } } @@ -556,7 +556,7 @@ impl StakingLedger { use sp_staking::OnStakerSlash as _; use sp_std::ops::Div as _; if slash_amount.is_zero() { - return Zero::zero(); + return Zero::zero() } let mut remaining_slash = slash_amount; @@ -621,10 +621,10 @@ impl StakingLedger { slashed_unlocking.insert(chunk.era, chunk.value); if remaining_slash.is_zero() { - break; + break } } else { - break; // defensive, indices should always be in bounds. + break // defensive, indices should always be in bounds. } } diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index edf46d464471d..773d7ca9ddb16 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -124,7 +124,7 @@ impl SlashingSpans { pub(crate) fn end_span(&mut self, now: EraIndex) -> bool { let next_start = now + 1; if next_start <= self.last_start { - return false; + return false } let last_length = next_start - self.last_start; @@ -236,7 +236,7 @@ pub(crate) fn compute_slash( // kick out the validator even if they won't be slashed, // as long as the misbehavior is from their most recent slashing span. kick_out_if_recent::(params); - return None; + return None } let (prior_slash_p, _era_slash) = @@ -259,7 +259,7 @@ pub(crate) fn compute_slash( // pays out some reward even if the latest report is not max-in-era. // we opt to avoid the nominator lookups and edits and leave more rewards // for more drastic misbehavior. - return None; + return None } // apply slash to validator. @@ -542,7 +542,7 @@ impl<'a, T: 'a + Config> Drop for InspectingSpans<'a, T> { fn drop(&mut self) { // only update on disk if we slashed this account. if !self.dirty { - return; + return } if let Some((start, end)) = self.spans.prune(self.window_start) { @@ -671,7 +671,7 @@ fn pay_reporters( // nobody to pay out to or nothing to pay; // just treat the whole value as slashed. T::Slash::on_unbalanced(slashed_imbalance); - return; + return } // take rewards out of the slashed imbalance. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index bf245e569c23e..3e999409ff664 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4819,15 +4819,7 @@ fn ledger_slash_works() { // Then assert_eq!(ledger.active, 500 / 2); - assert_eq!( - ledger.unlocking, - vec![ - c(0, 0), - c(1, 100 / 2), - c(2, 0), - c(3, 250 / 2) - ] - ); + assert_eq!(ledger.unlocking, vec![c(0, 0), c(1, 100 / 2), c(2, 0), c(3, 250 / 2)]); assert_eq!(ledger.total, 425); // Given we have the same as above, From f071b2df871f6ee16b6ff11e47f050038b3f3e5a Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sun, 6 Mar 2022 10:55:31 -0800 Subject: [PATCH 157/299] Update frame/staking/src/pallet/mod.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/staking/src/pallet/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index bc5207d91dda7..fb37b4aec1734 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -173,8 +173,8 @@ pub mod pallet { #[pallet::constant] type MaxUnlockingChunks: Get; - /// A hook called when any staker is slashed. Mostly likely this can be a no-op unless - /// there are nomination pools. + /// A hook called when any staker is slashed. Mostly likely this can be a no-op unless + /// other pallets exist that are affected by slashing per-staker. type OnStakerSlash: sp_staking::OnStakerSlash>; /// Some parameters of the benchmarking. From 11a55247550022f1e3f2031d3fedb83dc233f6d1 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sun, 6 Mar 2022 15:06:34 -0800 Subject: [PATCH 158/299] Tests: account for storing unbond era --- frame/nomination-pools/src/lib.rs | 11 ++--- frame/nomination-pools/src/tests.rs | 69 ++++++++++++++++------------- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 59c8b03bd3b46..88270235aa55f 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -764,10 +764,9 @@ pub struct SubPools { } impl SubPools { - /// Merge the oldest unbonding pool with an era into the general unbond pool with no associated - /// era. - fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self { - if current_era < TotalUnbondingPools::::get().into() { + /// Merge the oldest `with_era` unbond pools into the `no_era` unbond pool. + fn maybe_merge_pools(mut self, unbond_era: EraIndex) -> Self { + if unbond_era < TotalUnbondingPools::::get().into() { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. @@ -776,7 +775,7 @@ impl SubPools { // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools // 6..=10. - let newest_era_to_remove = current_era.saturating_sub(TotalUnbondingPools::::get()); + let newest_era_to_remove = unbond_era.saturating_sub(TotalUnbondingPools::::get()); let eras_to_remove: Vec<_> = self .with_era @@ -1137,6 +1136,8 @@ pub mod pallet { // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPoolsStorage::::get(&delegator.pool).unwrap_or_default(); let current_era = T::StakingInterface::current_era(); + // TODO: [now] look into removing bonding_duration and instead exposing + // `StakingInterface::unbond_era_for(current)` let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); let balance_to_unbond = bonded_pool.balance_to_unbond(delegator.points); diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 18ed1cbcf34f3..a684acecdd49a 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -20,6 +20,7 @@ fn test_setup_works() { assert_eq!(RewardPools::::count(), 1); assert_eq!(SubPoolsStorage::::count(), 0); assert_eq!(Delegators::::count(), 1); + assert_eq!(StakingMock::bonding_duration(), 3); assert_eq!( BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), @@ -742,7 +743,7 @@ mod claim_payout { let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); let reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); - delegator.unbonding_era = Some(0); + delegator.unbonding_era = Some(0 + 3); assert_noop!( Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator), @@ -1223,7 +1224,7 @@ mod unbond { assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, - sub_pools_with_era! { 0 => UnbondPool:: { points: 10, balance: 10 }} + sub_pools_with_era! { 0 + 3 => UnbondPool:: { points: 10, balance: 10 }} ); assert_eq!( @@ -1260,7 +1261,7 @@ mod unbond { // Then assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, - sub_pools_with_era! { 0 => UnbondPool { points: 6, balance: 6 }} + sub_pools_with_era! { 0 + 3 => UnbondPool { points: 6, balance: 6 }} ); assert_eq!( BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), @@ -1276,7 +1277,7 @@ mod unbond { } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 94); - assert_eq!(Delegators::::get(40).unwrap().unbonding_era, Some(0)); + assert_eq!(Delegators::::get(40).unwrap().unbonding_era, Some(0 + 3)); assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding // When @@ -1286,7 +1287,7 @@ mod unbond { // Then assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, - sub_pools_with_era! { 0 => UnbondPool { points: 98, balance: 98 }} + sub_pools_with_era! { 0 + 3 => UnbondPool { points: 98, balance: 98 }} ); assert_eq!( BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), @@ -1302,7 +1303,7 @@ mod unbond { } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 2); - assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0)); + assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0 + 3)); assert_eq!(Balances::free_balance(&550), 550 + 550); // When @@ -1311,7 +1312,7 @@ mod unbond { // Then assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, - sub_pools_with_era! { 0 => UnbondPool { points: 100, balance: 100 }} + sub_pools_with_era! { 0 + 3 => UnbondPool { points: 100, balance: 100 }} ); assert_eq!( BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), @@ -1327,23 +1328,24 @@ mod unbond { } ); assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); - assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0)); + assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0 + 3)); assert_eq!(Balances::free_balance(&550), 550 + 550); }); } #[test] - fn unbond_merges_older_pools() { + fn unbond_other_merges_older_pools() { ExtBuilder::default().build_and_execute(|| { // Given + assert_eq!(StakingMock::bonding_duration(), 3); SubPoolsStorage::::insert( PRIMARY_ACCOUNT, SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { - 0 => UnbondPool { balance: 10, points: 100 }, - 1 => UnbondPool { balance: 20, points: 20 }, - 2 => UnbondPool { balance: 101, points: 101} + 0 + 3 => UnbondPool { balance: 10, points: 100 }, + 1 + 3 => UnbondPool { balance: 20, points: 20 }, + 2 + 3 => UnbondPool { balance: 101, points: 101} }, }, ); @@ -1361,8 +1363,8 @@ mod unbond { SubPools { no_era: UnbondPool { balance: 10 + 20, points: 100 + 20 }, with_era: sub_pools_with_era! { - 2 => UnbondPool { balance: 101, points: 101}, - current_era => UnbondPool { balance: 10, points: 10 }, + 2 + 3 => UnbondPool { balance: 101, points: 101}, + current_era + 3 => UnbondPool { balance: 10, points: 10 }, }, }, ) @@ -1413,7 +1415,7 @@ mod unbond { SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { - 0 => UnbondPool { points: 100 + 200, balance: 100 + 200 } + 0 + 3 => UnbondPool { points: 100 + 200, balance: 100 + 200 } }, } ); @@ -1470,7 +1472,7 @@ mod unbond { SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { - 0 => UnbondPool { points: 110, balance: 110 } + 0 + 3 => UnbondPool { points: 110, balance: 110 } } } ); @@ -1565,6 +1567,7 @@ mod withdraw_unbonded_other { .add_delegators(vec![(40, 40), (550, 550)]) .build_and_execute(|| { // Given + assert_eq!(StakingMock::bonding_duration(), 3); assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 600); @@ -1576,7 +1579,9 @@ mod withdraw_unbonded_other { assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); let mut sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(); - let unbond_pool = sub_pools.with_era.get_mut(¤t_era).unwrap(); + // TODO: [now] in the future we could use StakingMock::unbond_era_for(current_era) + // instead of current_era + 3. + let unbond_pool = sub_pools.with_era.get_mut(&(current_era + 3)).unwrap(); // Sanity check assert_eq!(*unbond_pool, UnbondPool { points: 10, balance: 10 }); @@ -1598,13 +1603,13 @@ mod withdraw_unbonded_other { // `no_era` let sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT) .unwrap() - .maybe_merge_pools(current_era); + .maybe_merge_pools(current_era + 3); SubPoolsStorage::::insert(PRIMARY_ACCOUNT, sub_pools); assert_eq!( SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), SubPools { - with_era: Default::default(), - no_era: UnbondPool { points: 550 + 40 + 10, balance: 550 + 40 + 5 } + no_era: UnbondPool { points: 550 + 40 + 10, balance: 550 + 40 + 5 }, + with_era: Default::default() } ); @@ -1666,7 +1671,7 @@ mod withdraw_unbonded_other { PRIMARY_ACCOUNT, SubPools { no_era: Default::default(), - with_era: sub_pools_with_era! { 0 => UnbondPool { points: 600, balance: 100 }}, + with_era: sub_pools_with_era! { 0 + 3 => UnbondPool { points: 600, balance: 100 }}, }, ); CurrentEra::set(StakingMock::bonding_duration()); @@ -1677,7 +1682,7 @@ mod withdraw_unbonded_other { // Then assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, - sub_pools_with_era! { 0 => UnbondPool { points: 560, balance: 94 }} + sub_pools_with_era! { 0 + 3 => UnbondPool { points: 560, balance: 94 }} ); assert_eq!(Balances::free_balance(&40), 40 + 6); assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 94); @@ -1689,7 +1694,7 @@ mod withdraw_unbonded_other { // Then assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, - sub_pools_with_era! { 0 => UnbondPool { points: 10, balance: 2 }} + sub_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 2 }} ); assert_eq!(Balances::free_balance(&550), 550 + 92); // The account was dusted because it went below ED(5) @@ -1712,11 +1717,11 @@ mod withdraw_unbonded_other { #[test] fn withdraw_unbonded_other_errors_correctly() { - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().build_and_execute_no_checks(|| { // Insert the sub-pool let sub_pools = SubPools { no_era: Default::default(), - with_era: sub_pools_with_era! { 0 => UnbondPool { points: 10, balance: 10 }}, + with_era: sub_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 }}, }; SubPoolsStorage::::insert(123, sub_pools.clone()); @@ -1740,7 +1745,7 @@ mod withdraw_unbonded_other { ); // Simulate calling `unbond` - delegator.unbonding_era = Some(0); + delegator.unbonding_era = Some(0 + 3); Delegators::::insert(11, delegator.clone()); // We are still in the bonding duration @@ -1873,8 +1878,8 @@ mod withdraw_unbonded_other { SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { - 0 => UnbondPool { points: 100, balance: 100}, - 1 => UnbondPool { points: 200 + 10, balance: 200 + 10 } + 0 + 3 => UnbondPool { points: 100, balance: 100}, + 1 + 3 => UnbondPool { points: 200 + 10, balance: 200 + 10 } } } ); @@ -1895,8 +1900,8 @@ mod withdraw_unbonded_other { SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { - // Note that era 0 unbond pool is destroyed because points went to 0 - 1 => UnbondPool { points: 200 + 10, balance: 200 + 10 } + // Note that era 0+3 unbond pool is destroyed because points went to 0 + 1 + 3 => UnbondPool { points: 200 + 10, balance: 200 + 10 } } } ); @@ -1914,7 +1919,7 @@ mod withdraw_unbonded_other { SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { - 1 => UnbondPool { points: 10, balance: 10 } + 1 + 3 => UnbondPool { points: 10, balance: 10 } } } ); @@ -1945,7 +1950,7 @@ mod withdraw_unbonded_other { // Simulate some other withdraw that caused the pool to merge let sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT) .unwrap() - .maybe_merge_pools(current_era); + .maybe_merge_pools(current_era + 3); SubPoolsStorage::::insert(&PRIMARY_ACCOUNT, sub_pools); assert_eq!( SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), From 81aae81ab03dc8ca92eda4c2db97f494144cd8e9 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sun, 6 Mar 2022 15:57:42 -0800 Subject: [PATCH 159/299] Improve docs for staking interface --- frame/nomination-pools/src/lib.rs | 4 +- primitives/staking/src/lib.rs | 64 +++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 88270235aa55f..832bc471919fc 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -297,9 +297,7 @@ // * A pool's `delegator_counter` should always be gt 0. // TODO -// - counter for delegators should never be below 1 (below 1 means the pool should be destroyed) // - write detailed docs for StakingInterface -// - slashing - test for correct input to hook (these tests need to be in staking side) // - transparent prefx for account ids // Ensure we're `no_std` when compiling for Wasm. @@ -1136,7 +1134,7 @@ pub mod pallet { // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPoolsStorage::::get(&delegator.pool).unwrap_or_default(); let current_era = T::StakingInterface::current_era(); - // TODO: [now] look into removing bonding_duration and instead exposing + // TODO: [now] look into removing bonding_duration and instead exposing // `StakingInterface::unbond_era_for(current)` let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 00e905331bb71..a49bfa8d41548 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -61,39 +61,41 @@ pub trait StakingInterface { /// AccountId type used by the staking system type AccountId; + /// The type for the `validators` argument to `Self::nominate`. type LookupSource; - /// The minimum amount necessary to bond to be a nominator. This does not necessarily mean the - /// nomination will be counted in an election, but instead just enough to be stored as a - /// nominator (e.g. in the bags-list of polkadot) + /// The minimum amount required to bond in order to be a nominator. This does not necessarily + /// mean the nomination will be counted in an election, but instead just enough to be stored as + /// a nominator. In other words, this is the minimum amount to register the intention to + /// nominate. fn minimum_bond() -> Self::Balance; - /// Number of eras that staked funds must remain bonded for. NOTE: it is assumed that this is - /// always strictly greater than the slash deffer duration. + /// Number of eras that staked funds must remain bonded for. + /// + /// # Note + /// + /// This must be strictly greater than the staking systems slash deffer duration. fn bonding_duration() -> EraIndex; - /// The current era for the staking system. + /// The current era index. + /// + /// This should be the latest planned era that the staking system knows about. fn current_era() -> EraIndex; /// Balance `controller` has bonded for nominating. fn bonded_balance(controller: &Self::AccountId) -> Option; - /// Balance `controller` has locked by the staking system. This is the bonded funds and the - /// unlocking funds and thus is a superset of bonded funds. + /// Balance the _Stash_ linked to `controller` has locked by the staking system. This should + /// include both the users bonded funds and their unlocking funds. + /// + /// # Note + /// + /// This is only guaranteed to reflect the amount locked by the staking system. If there are + /// non-staking locks on the bonded pair's balance this may not be accurate. fn locked_balance(controller: &Self::AccountId) -> Option; - fn bond_extra(controller: Self::AccountId, extra: Self::Balance) -> DispatchResult; - - fn unbond(controller: Self::AccountId, value: Self::Balance) -> DispatchResult; - - /// Withdraw unbonded funds from bonded user. - fn withdraw_unbonded( - controller: Self::AccountId, - num_slashing_spans: u32, - ) -> Result; - - /// Bond the funds and create a `stash` and `controller`, a bond of `value`, and `payee` account - /// as the reward destination. + /// Bond (lock) `value` of `stash`'s balance. `controller` will be set as the account + /// controlling `stash`. This creates what is referred to as "bonded pair". fn bond( stash: Self::AccountId, controller: Self::AccountId, @@ -106,4 +108,26 @@ pub trait StakingInterface { controller: Self::AccountId, validators: sp_std::vec::Vec, ) -> DispatchResult; + + /// Bond some extra amount in the _Stash_'s free balance against the active bonded balance of + /// the account. The amount extra actually bonded will never be more than the _Stash_'s free + /// balance. + fn bond_extra(controller: Self::AccountId, extra: Self::Balance) -> DispatchResult; + + /// Schedule a portion of the active bonded balance to be unlocked at era + /// [Self::current_era] + [`Self::bonding_duration`]. + /// + /// Once the unlock era has been reached, [`Self::withdraw_unbonded`] can be called to unlock + /// the funds. + /// + /// The amount of times this can be successfully called is limited based on how many distinct + /// eras funds are schedule to unlock in. Calling [`Self::withdraw_unbonded`] after some unlock + /// schedules have reached their unlocking era should allow more calls to this function. + fn unbond(controller: Self::AccountId, value: Self::Balance) -> DispatchResult; + + /// Unlock any funds schedule to unlock before or at the current era. + fn withdraw_unbonded( + controller: Self::AccountId, + num_slashing_spans: u32, + ) -> Result; } From c2a5103e810a3e36859e8b6c57b4623cc594e95b Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sun, 6 Mar 2022 16:22:46 -0800 Subject: [PATCH 160/299] Add events Created, Destroyed --- frame/nomination-pools/src/lib.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 832bc471919fc..e287c3be08276 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -296,8 +296,7 @@ // * The sum of each pools delegator counter equals the `Delegators::count()`. // * A pool's `delegator_counter` should always be gt 0. -// TODO -// - write detailed docs for StakingInterface +// // - transparent prefx for account ids // Ensure we're `no_std` when compiling for Wasm. @@ -951,11 +950,13 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { + Created { pool: T::AccountId, depositor: T::AccountId }, Joined { delegator: T::AccountId, pool: T::AccountId, bonded: BalanceOf }, PaidOut { delegator: T::AccountId, pool: T::AccountId, payout: BalanceOf }, Unbonded { delegator: T::AccountId, pool: T::AccountId, amount: BalanceOf }, Withdrawn { delegator: T::AccountId, pool: T::AccountId, amount: BalanceOf }, DustWithdrawn { delegator: T::AccountId, pool: T::AccountId }, + Destroyed { pool: T::AccountId }, } #[pallet::error] @@ -1134,8 +1135,6 @@ pub mod pallet { // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPoolsStorage::::get(&delegator.pool).unwrap_or_default(); let current_era = T::StakingInterface::current_era(); - // TODO: [now] look into removing bonding_duration and instead exposing - // `StakingInterface::unbond_era_for(current)` let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); let balance_to_unbond = bonded_pool.balance_to_unbond(delegator.points); @@ -1258,6 +1257,7 @@ pub mod pallet { // all the bonded balance and balance in unlocking chunks .min(bonded_pool.non_locked_balance()); + // TODO: [now] this check probably isn't necessary if balance_to_unbond >= T::Currency::minimum_balance() { T::Currency::transfer( &delegator.pool, @@ -1288,6 +1288,7 @@ pub mod pallet { let post_info_weight = if should_remove_pool { let reward_pool = RewardPools::::take(&delegator.pool) .defensive_ok_or_else(|| Error::::PoolNotFound)?; + Self::deposit_event(Event::::Destroyed { pool: delegator.pool.clone() }); SubPoolsStorage::::remove(&delegator.pool); // Kill accounts from storage by making their balance go below ED. We assume that // the accounts have no references that would prevent destruction once we get to @@ -1295,7 +1296,6 @@ pub mod pallet { T::Currency::make_free_balance_be(&reward_pool.account, Zero::zero()); T::Currency::make_free_balance_be(&bonded_pool.account, Zero::zero()); bonded_pool.remove(); - // TODO: destroy event None } else { bonded_pool.dec_delegators().put(); @@ -1362,6 +1362,10 @@ pub mod pallet { bonded_pool.reward_account(), )?; + Self::deposit_event(Event::::Created { + depositor: who.clone(), + pool: bonded_pool.account.clone(), + }); Delegators::::insert( who, Delegator:: { From c00242808dd95db570974922730fe532cb9cb607 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sun, 6 Mar 2022 19:44:31 -0800 Subject: [PATCH 161/299] withdraw_unbonded: Remove useless withdraw dust check --- frame/nomination-pools/src/lib.rs | 42 +++++++++---------------------- 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index e287c3be08276..091cd7a72b5e4 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -955,7 +955,6 @@ pub mod pallet { PaidOut { delegator: T::AccountId, pool: T::AccountId, payout: BalanceOf }, Unbonded { delegator: T::AccountId, pool: T::AccountId, amount: BalanceOf }, Withdrawn { delegator: T::AccountId, pool: T::AccountId, amount: BalanceOf }, - DustWithdrawn { delegator: T::AccountId, pool: T::AccountId }, Destroyed { pool: T::AccountId }, } @@ -1257,33 +1256,18 @@ pub mod pallet { // all the bonded balance and balance in unlocking chunks .min(bonded_pool.non_locked_balance()); - // TODO: [now] this check probably isn't necessary - if balance_to_unbond >= T::Currency::minimum_balance() { - T::Currency::transfer( - &delegator.pool, - &target, - balance_to_unbond, - ExistenceRequirement::AllowDeath, - ) - .defensive_map_err(|e| e)?; - Self::deposit_event(Event::::Withdrawn { - delegator: target.clone(), - pool: delegator.pool.clone(), - amount: balance_to_unbond, - }); - } else { - //This should only happen if 1) a previous withdraw put the pools balance - // below ED and it was dusted or 2) the pool was slashed a huge amount that wiped - // all the unlocking chunks and bonded balance, thus causing inconsistencies with - // unbond pool's tracked balance and the actual balance (if this happens, the pool - // is in an invalid state anyways because there are no bonded funds so no one can - // join). We gracefully carry on, primarily to ensure the pool can eventually be - // destroyed - Self::deposit_event(Event::::DustWithdrawn { - delegator: target.clone(), - pool: delegator.pool.clone(), - }); - } + T::Currency::transfer( + &delegator.pool, + &target, + balance_to_unbond, + ExistenceRequirement::AllowDeath, + ) + .defensive_map_err(|e| e)?; + Self::deposit_event(Event::::Withdrawn { + delegator: target.clone(), + pool: delegator.pool.clone(), + amount: balance_to_unbond, + }); let post_info_weight = if should_remove_pool { let reward_pool = RewardPools::::take(&delegator.pool) @@ -1386,8 +1370,6 @@ pub mod pallet { ); bonded_pool.put(); - // TODO: event - Ok(()) } From eb3cbc6804969e58e881fec5a3612064686e6021 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sun, 6 Mar 2022 20:43:02 -0800 Subject: [PATCH 162/299] Test: withdraw_unbonded_other_handles_faulty_sub_pool_accounting --- frame/nomination-pools/src/lib.rs | 14 +++++++++----- frame/nomination-pools/src/tests.rs | 30 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 091cd7a72b5e4..2b92c5c8841d0 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -296,7 +296,7 @@ // * The sum of each pools delegator counter equals the `Delegators::count()`. // * A pool's `delegator_counter` should always be gt 0. -// +// // - transparent prefx for account ids // Ensure we're `no_std` when compiling for Wasm. @@ -617,7 +617,7 @@ impl BondedPool { Ok(()) } - /// Returns a result indicating if `Call::withdraw_unbonded_other` can be executed. + /// Returns a result indicating if [`Pallet::withdraw_unbonded_other`] can be executed. fn ok_to_withdraw_unbonded_other_with( &self, caller: &T::AccountId, @@ -1251,9 +1251,13 @@ pub mod pallet { balance_to_unbond } - // TODO: make sure this is a test for this edge case - // We can get here in the rare case a pool had such an extreme slash that it erased - // all the bonded balance and balance in unlocking chunks + // A call to this function may cause the pool's stash to get dusted. If this happens + // before the last delegator has withdrawn, then all subsequent withdraws will be 0. + // However the unbond pools do no get updated to reflect this. In the aforementioned + // scenario, this check ensures we don't try to withdraw funds that don't exist. + // This check is also defensive in cases where the unbond pool does not update its + // balance (e.g. a bug in the slashing hook.) We gracefully proceed in + // order to ensure delegators can leave the pool and it can be destroyed. .min(bonded_pool.non_locked_balance()); T::Currency::transfer( diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index a684acecdd49a..adbcf9c2137b3 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1661,6 +1661,7 @@ mod withdraw_unbonded_other { // Given StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); // slash bonded balance Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 100); + assert_eq!(StakingMock::locked_balance(&PRIMARY_ACCOUNT), Some(100)); assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); @@ -1715,6 +1716,35 @@ mod withdraw_unbonded_other { }); } + #[test] + fn withdraw_unbonded_other_handles_faulty_sub_pool_accounting() { + ExtBuilder::default().build_and_execute(|| { + // Given + assert_eq!(Balances::minimum_balance(), 5); + assert_eq!(Balances::free_balance(&10), 10); + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 10); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); + + // Simulate a slash that is not accounted for in the sub pools. + Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 5); + assert_eq!( + SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, + //------------------------------balance decrease is not account for + sub_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 } } + ); + + CurrentEra::set(0 + 3); + + // When + assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(10), 10, 0)); + + // Then + assert_eq!(Balances::free_balance(10), 10 + 5); + assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 0); + }); + } + #[test] fn withdraw_unbonded_other_errors_correctly() { ExtBuilder::default().build_and_execute_no_checks(|| { From 719496cc69d3f2fd3a96406c178d3c6e578eaad5 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sun, 6 Mar 2022 21:57:18 -0800 Subject: [PATCH 163/299] Add extrinsics: set_state_other, set_metadata --- frame/nomination-pools/src/lib.rs | 133 +++++++++++++++++++++++------ frame/nomination-pools/src/mock.rs | 2 + 2 files changed, 109 insertions(+), 26 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 2b92c5c8841d0..31675b300476f 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -544,9 +544,9 @@ impl BondedPool { points_to_issue } - /// Check that the pool can accept a member with `new_funds`. - fn ok_to_join_with(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { - ensure!(self.state == PoolState::Open, Error::::NotOpen); + /// Whether or not the pool is ok to be in `PoolSate::Open`. If this return an `Err`, then the + /// pool is unrecoverable and should be to a destroying state. + fn ok_to_be_open(&self) -> Result<(), DispatchError> { let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); @@ -565,12 +565,29 @@ impl BondedPool { ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, ensure!( - new_funds.saturating_add(bonded_balance) < - BalanceOf::::max_value().div(10u32.into()), + bonded_balance < BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow // `BalanceOf`. + + Ok(()) + } + + /// Check that the pool can accept a member with `new_funds`. + fn ok_to_join_with(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { + ensure!(self.state == PoolState::Open, Error::::NotOpen); + + self.ok_to_be_open()?; + + let bonded_balance = + T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); + ensure!( + new_funds.saturating_add(bonded_balance) < + BalanceOf::::max_value().div(10u32.into()), + Error::::OverflowRisk + ); + Ok(()) } @@ -582,6 +599,14 @@ impl BondedPool { *who == self.root || *who == self.state_toggler && self.state == PoolState::Blocked } + fn can_toggle_state(&self, who: &T::AccountId) -> bool { + *who == self.root || *who == self.state_toggler && self.state != PoolState::Destroying + } + + fn can_set_metadata(&self, who: &T::AccountId) -> bool { + *who == self.root || *who == self.state_toggler + } + fn is_destroying(&self) -> bool { self.state == PoolState::Destroying } @@ -765,12 +790,12 @@ impl SubPools { fn maybe_merge_pools(mut self, unbond_era: EraIndex) -> Self { if unbond_era < TotalUnbondingPools::::get().into() { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do - // anything. I.E. if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool + // anything. Ex: if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. return self } - // I.E. if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools + // Ex: if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools // 6..=10. let newest_era_to_remove = unbond_era.saturating_sub(TotalUnbondingPools::::get()); @@ -860,6 +885,9 @@ pub mod pallet { /// points to balance; once the `with_era` pool is merged into the `no_era` pool, the ratio /// can become skewed due to some slashed ratio getting merged in at some point. type PostUnbondingPoolsWindow: Get; + + /// The maximum length, in bytes, that a pools metadata maybe. + type MaxMetadataLen: Get; } /// Minimum amount to bond to join a pool. @@ -908,6 +936,16 @@ pub mod pallet { pub type SubPoolsStorage = CountedStorageMap<_, Twox64Concat, T::AccountId, SubPools>; + /// Metadata for the pool + #[pallet::storage] + pub type Metadata = CountedStorageMap< + _, + Twox64Concat, + T::AccountId, + BoundedVec, + ValueQuery, + >; + #[pallet::genesis_config] pub struct GenesisConfig { pub min_join_bond: BalanceOf, @@ -978,7 +1016,7 @@ pub mod pallet { AlreadyUnbonding, /// The delegator is not unbonding and thus cannot withdraw funds. NotUnbonding, - /// Unbonded funds cannot be withdrawn yet because the bond duration has not passed. + /// Unbonded funds cannot be withdrawn yet because the bonding duration has not passed. NotUnbondedYet, /// The amount does not meet the minimum bond to either join or create a pool. MinimumBondNotMet, @@ -995,7 +1033,7 @@ pub mod pallet { NotOnlyDelegator, /// The caller does not have nominating permissions for the pool. NotNominator, - /// Either a) the caller cannot make a valid kick or b) the pool is not destroying + /// Either a) the caller cannot make a valid kick or b) the pool is not destroying. NotKickerOrDestroying, /// The pool is not open to join NotOpen, @@ -1003,6 +1041,10 @@ pub mod pallet { MaxPools, /// Too many delegators in the pool or system. MaxDelegators, + /// The pools state cannot be changed. + CanNotChangeState, + /// The caller does not have adequate permissions. + DoesNotHavePermission, } #[pallet::call] @@ -1391,23 +1433,62 @@ pub mod pallet { Ok(()) } - // pub fn set_state_other(origin: OriginFor, pool_account: T::AccountId, state: - // PoolState) -> DispatchError { let who = ensure_signed!(origin); - // BondedPool::::try_mutate(pool_account, |maybe_bonded_pool| { - // maybe_bonded_pool.ok_or(Error::::PoolNotFound).map(|bonded_pool| - // if bonded_pool.is_destroying() { - // // invariant, a destroying pool cannot become non-destroying - // // this is because - // Err(Error::::Err)? - // } - - // if bonded_pool.is_spoiled() && state == PoolState::Destroying { - // bonded_pool.state = PoolState::Destroying - // } else if bonded_pool.root == who || bonded_pool.state_toggler == who { - // bonded_pool.state = who - // } - // ) - // }) + #[pallet::weight(42)] // TODO: [now] add bench for this + pub fn set_state_other( + origin: OriginFor, + pool_account: T::AccountId, + state: PoolState, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let mut bonded_pool = + BondedPool::::get(&pool_account).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.state != PoolState::Destroying, Error::::CanNotChangeState); + + // TODO: [now] we could check if bonded_pool.ok_to_be_open().is_err(), and if thats + // true always set the state to destroying, regardless of the stat the caller passes. + // The downside is that this seems like a misleading API + + if bonded_pool.can_toggle_state(&who) { + bonded_pool.state = state + } else if bonded_pool.ok_to_be_open().is_err() && state == PoolState::Destroying { + // If the pool has bad properties, then anyone can set it as destroying + bonded_pool.state = PoolState::Destroying; + } else { + Err(Error::::CanNotChangeState)?; + } + + bonded_pool.put(); + + Ok(()) + } + + #[pallet::weight(42)] // TODO: [now] add bench for this + pub fn set_metadata( + origin: OriginFor, + pool_account: T::AccountId, + metadata: BoundedVec, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + ensure!( + BondedPool::::get(&pool_account) + .ok_or(Error::::PoolNotFound)? + .can_set_metadata(&who), + Error::::DoesNotHavePermission + ); + + Metadata::::mutate(&pool_account, |pool_meta| *pool_meta = metadata); + + Ok(()) + } + + // Set + // * `min_join_bond` + // * `min_create_bond` + // * `max_pools` + // * `max_delegators_per_pool` + // * `max_delegators` + // pub fn set_parameters(origin: OriginFor, ) -> DispatchResult { + // } } diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index aab997cabe5e8..72c2a17c925e7 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -152,6 +152,7 @@ impl Convert for U256ToBalance { parameter_types! { pub static PostUnbondingPoolsWindow: u32 = 2; + pub static MaxMetadataLen: u32 = 2; } impl pools::Config for Runtime { @@ -162,6 +163,7 @@ impl pools::Config for Runtime { type U256ToBalance = U256ToBalance; type StakingInterface = StakingMock; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; + type MaxMetadataLen = MaxMetadataLen; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; From 9bcf2710a224d69a6a75dde9ce84a59a60b23a2d Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sun, 6 Mar 2022 23:07:17 -0800 Subject: [PATCH 164/299] Test: set_state_other_works --- frame/nomination-pools/src/lib.rs | 40 +++++++----- frame/nomination-pools/src/tests.rs | 98 +++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 17 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 31675b300476f..8c47be672ea5a 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -296,7 +296,6 @@ // * The sum of each pools delegator counter equals the `Delegators::count()`. // * A pool's `delegator_counter` should always be gt 0. -// // - transparent prefx for account ids // Ensure we're `no_std` when compiling for Wasm. @@ -344,8 +343,9 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), + (true, true) | (false, true) => { + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) + }, (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -370,7 +370,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero() + return Zero::zero(); } // Equivalent of (current_balance / current_points) * delegator_points @@ -544,9 +544,11 @@ impl BondedPool { points_to_issue } - /// Whether or not the pool is ok to be in `PoolSate::Open`. If this return an `Err`, then the - /// pool is unrecoverable and should be to a destroying state. + /// Whether or not the pool is ok to be in `PoolSate::Open`. If this returns an `Err`, then the + /// pool is unrecoverable and should be in the destroying state. fn ok_to_be_open(&self) -> Result<(), DispatchError> { + ensure!(!self.is_destroying(), Error::::CanNotChangeState); + let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); @@ -583,8 +585,8 @@ impl BondedPool { let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); ensure!( - new_funds.saturating_add(bonded_balance) < - BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) + < BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); @@ -600,7 +602,7 @@ impl BondedPool { } fn can_toggle_state(&self, who: &T::AccountId) -> bool { - *who == self.root || *who == self.state_toggler && self.state != PoolState::Destroying + *who == self.root || *who == self.state_toggler && !self.is_destroying() } fn can_set_metadata(&self, who: &T::AccountId) -> bool { @@ -792,7 +794,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. Ex: if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self + return self; } // Ex: if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -1045,6 +1047,8 @@ pub mod pallet { CanNotChangeState, /// The caller does not have adequate permissions. DoesNotHavePermission, + /// Metadata exceeds [`T::MaxMetadataLen`] + MetadataExceedsMaxLen, } #[pallet::call] @@ -1360,8 +1364,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() && - amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() + && amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1466,9 +1470,11 @@ pub mod pallet { pub fn set_metadata( origin: OriginFor, pool_account: T::AccountId, - metadata: BoundedVec, + metadata: Vec, ) -> DispatchResult { let who = ensure_signed(origin)?; + let metadata: BoundedVec<_, _> = + metadata.try_into().map_err(|_| Error::::MetadataExceedsMaxLen)?; ensure!( BondedPool::::get(&pool_account) .ok_or(Error::::PoolNotFound)? @@ -1541,9 +1547,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() || - current_points.is_zero() || - reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() + || current_points.is_zero() + || reward_pool.balance.is_zero() { Zero::zero() } else { @@ -1607,7 +1613,7 @@ impl Pallet { impl OnStakerSlash> for Pallet { fn on_slash( pool_account: &T::AccountId, - _slashed_bonded: BalanceOf, // bonded balance is always read directly from staking. + _slashed_bonded: BalanceOf, // Bonded balance is always read directly from staking. slashed_unlocking: &BTreeMap>, ) { let mut sub_pools = match SubPoolsStorage::::get(pool_account) { diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index adbcf9c2137b3..c462c4ee7bdc0 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -2172,3 +2172,101 @@ mod nominate { }); } } + +mod set_state_other { + use super::*; + + #[test] + fn set_state_other_works() { + ExtBuilder::default().build_and_execute(|| { + // Only the root and state_toggler can change the state when the pool is ok to be open. + assert_ok!(BondedPool::::get(&PRIMARY_ACCOUNT).unwrap().ok_to_be_open()); + assert_noop!( + Pools::set_state_other(Origin::signed(10), PRIMARY_ACCOUNT, PoolState::Blocked), + Error::::CanNotChangeState + ); + assert_noop!( + Pools::set_state_other(Origin::signed(901), PRIMARY_ACCOUNT, PoolState::Blocked), + Error::::CanNotChangeState + ); + + assert_ok!(Pools::set_state_other( + Origin::signed(900), + PRIMARY_ACCOUNT, + PoolState::Blocked + )); + assert_eq!( + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap().state, + PoolState::Blocked + ); + + assert_ok!(Pools::set_state_other( + Origin::signed(902), + PRIMARY_ACCOUNT, + PoolState::Destroying + )); + assert_eq!( + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap().state, + PoolState::Destroying + ); + + // If the pool is destroying, then no one can set state + assert_noop!( + Pools::set_state_other(Origin::signed(900), PRIMARY_ACCOUNT, PoolState::Blocked), + Error::::CanNotChangeState + ); + assert_noop!( + Pools::set_state_other(Origin::signed(902), PRIMARY_ACCOUNT, PoolState::Blocked), + Error::::CanNotChangeState + ); + + // If the pool is not ok to be open, then anyone can set it to destroying + + // Given + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Open).unwrap(); + let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + bonded_pool.points = 100; + bonded_pool.put(); + // When + assert_ok!(Pools::set_state_other( + Origin::signed(11), + PRIMARY_ACCOUNT, + PoolState::Destroying + )); + // Then + assert_eq!( + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap().state, + PoolState::Destroying + ); + + // Given + Balances::make_free_balance_be(&PRIMARY_ACCOUNT, Balance::max_value() / 10); + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Open).unwrap(); + // When + assert_ok!(Pools::set_state_other( + Origin::signed(11), + PRIMARY_ACCOUNT, + PoolState::Destroying + )); + // Then + assert_eq!( + BondedPool::::get(&PRIMARY_ACCOUNT).unwrap().state, + PoolState::Destroying + ); + + // If the pool is not ok to be open, it cannot be permissionleslly set to a state that isn't destroying + unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Open).unwrap(); + assert_noop!( + Pools::set_state_other(Origin::signed(11), PRIMARY_ACCOUNT, PoolState::Blocked), + Error::::CanNotChangeState + ); + }); + } +} + +mod set_metadata { + use super::*; + + #[test] + fn set_metadata_works() {} +} From 1d5f42b91b1c8cf6c7370cd87770af07056e77ba Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sun, 6 Mar 2022 23:43:30 -0800 Subject: [PATCH 165/299] Test: set_metadata_works --- frame/nomination-pools/src/lib.rs | 24 +++++++++---------- frame/nomination-pools/src/tests.rs | 37 ++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 8c47be672ea5a..111ed116ab866 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -296,6 +296,7 @@ // * The sum of each pools delegator counter equals the `Delegators::count()`. // * A pool's `delegator_counter` should always be gt 0. +// // - transparent prefx for account ids // Ensure we're `no_std` when compiling for Wasm. @@ -343,9 +344,8 @@ fn points_to_issue( new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => { - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()) - }, + (true, true) | (false, true) => + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. @@ -370,7 +370,7 @@ fn balance_to_unbond( ) -> BalanceOf { if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond - return Zero::zero(); + return Zero::zero() } // Equivalent of (current_balance / current_points) * delegator_points @@ -585,8 +585,8 @@ impl BondedPool { let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); ensure!( - new_funds.saturating_add(bonded_balance) - < BalanceOf::::max_value().div(10u32.into()), + new_funds.saturating_add(bonded_balance) < + BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); @@ -794,7 +794,7 @@ impl SubPools { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. Ex: if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool // for this era and have exactly `TotalUnbondingPools` pools. - return self; + return self } // Ex: if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools @@ -1364,8 +1364,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() - && amount >= MinCreateBond::::get(), + amount >= T::StakingInterface::minimum_bond() && + amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); if let Some(max_pools) = MaxPools::::get() { @@ -1547,9 +1547,9 @@ impl Pallet { let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() - || current_points.is_zero() - || reward_pool.balance.is_zero() + let delegator_payout = if delegator_virtual_points.is_zero() || + current_points.is_zero() || + reward_pool.balance.is_zero() { Zero::zero() } else { diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index c462c4ee7bdc0..b4d36ce0a425b 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -2179,7 +2179,7 @@ mod set_state_other { #[test] fn set_state_other_works() { ExtBuilder::default().build_and_execute(|| { - // Only the root and state_toggler can change the state when the pool is ok to be open. + // Only the root and state toggler can change the state when the pool is ok to be open. assert_ok!(BondedPool::::get(&PRIMARY_ACCOUNT).unwrap().ok_to_be_open()); assert_noop!( Pools::set_state_other(Origin::signed(10), PRIMARY_ACCOUNT, PoolState::Blocked), @@ -2190,6 +2190,7 @@ mod set_state_other { Error::::CanNotChangeState ); + // Root can change state assert_ok!(Pools::set_state_other( Origin::signed(900), PRIMARY_ACCOUNT, @@ -2200,6 +2201,7 @@ mod set_state_other { PoolState::Blocked ); + // State toggler can change state assert_ok!(Pools::set_state_other( Origin::signed(902), PRIMARY_ACCOUNT, @@ -2254,7 +2256,8 @@ mod set_state_other { PoolState::Destroying ); - // If the pool is not ok to be open, it cannot be permissionleslly set to a state that isn't destroying + // If the pool is not ok to be open, it cannot be permissionleslly set to a state that + // isn't destroying unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Open).unwrap(); assert_noop!( Pools::set_state_other(Origin::signed(11), PRIMARY_ACCOUNT, PoolState::Blocked), @@ -2268,5 +2271,33 @@ mod set_metadata { use super::*; #[test] - fn set_metadata_works() {} + fn set_metadata_works() { + ExtBuilder::default().build_and_execute(|| { + // Root can set metadata + assert_ok!(Pools::set_metadata(Origin::signed(900), PRIMARY_ACCOUNT, vec![1, 1])); + assert_eq!(Metadata::::get(PRIMARY_ACCOUNT), vec![1, 1]); + + // State toggler can set metadata + assert_ok!(Pools::set_metadata(Origin::signed(902), PRIMARY_ACCOUNT, vec![2, 2])); + assert_eq!(Metadata::::get(PRIMARY_ACCOUNT), vec![2, 2]); + + // Depositor can't set metadata + assert_noop!( + Pools::set_metadata(Origin::signed(10), PRIMARY_ACCOUNT, vec![3, 3]), + Error::::DoesNotHavePermission + ); + + // Nominator can't set metadata + assert_noop!( + Pools::set_metadata(Origin::signed(901), PRIMARY_ACCOUNT, vec![3, 3]), + Error::::DoesNotHavePermission + ); + + // Metadata cannot be longer than `MaxMetadataLen` + assert_noop!( + Pools::set_metadata(Origin::signed(900), PRIMARY_ACCOUNT, vec![1, 1, 1]), + Error::::MetadataExceedsMaxLen + ); + }); + } } From 32e1038ac44859d58e9d5ee60a6a21addb359760 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 7 Mar 2022 10:28:29 +0100 Subject: [PATCH 166/299] Add benchmarks for set_state_other, set_metadata --- .../nomination-pools/benchmarking/src/lib.rs | 38 ++++++++++++++++++- .../nomination-pools/benchmarking/src/mock.rs | 1 + frame/nomination-pools/src/lib.rs | 2 +- frame/nomination-pools/src/weights.rs | 25 +++++++++++- 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 8d7f729625973..03292f1ad09fe 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -11,7 +11,7 @@ use frame_election_provider_support::SortedListProvider; use frame_support::{ensure, traits::Get}; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ - BalanceOf, BondedPoolStorage, BondedPools, Delegators, MinCreateBond, MinJoinBond, + BalanceOf, BondedPoolStorage, BondedPools, Delegators, Metadata, MinCreateBond, MinJoinBond, Pallet as Pools, PoolState, RewardPools, SubPoolsStorage, }; use sp_runtime::traits::{StaticLookup, Zero}; @@ -531,6 +531,42 @@ frame_benchmarking::benchmarks! { Some(min_create_bond) ); } + + set_state_other { + // Create a pool + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + BondedPools::::mutate(&pool_account, |maybe_pool| { + // Force the pool into an invalid state + maybe_pool.as_mut().map(|mut pool| pool.points = min_create_bond * 10u32.into()); + }); + + let caller = account("caller", 0, USER_SEED); + whitelist_account!(caller); + }:_(Origin::Signed(caller), pool_account.clone(), PoolState::Destroying) + verify { + assert_eq!(BondedPools::::get(pool_account).unwrap().state, PoolState::Destroying); + } + + set_metadata { + clear_storage::(); + + // Create a pool + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + + // Create metadata of the max possible size + let metadata: Vec = (0..::MaxMetadataLen::get()).map(|_| 42).collect(); + + whitelist_account!(depositor); + }:_(Origin::Signed(depositor), pool_account.clone(), metadata.clone()) + verify { + assert_eq!(Metadata::::get(&pool_account), metadata); + } } // TODO: consider benchmarking slashing logic with pools diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index cb20ed87b2925..0e89ed09fe6e1 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -151,6 +151,7 @@ impl pallet_nomination_pools::Config for Runtime { type U256ToBalance = U256ToBalance; type StakingInterface = Staking; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; + type MaxMetadataLen = ConstU32<256>; } impl crate::Config for Runtime {} diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 111ed116ab866..02cafd7c34699 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -296,7 +296,7 @@ // * The sum of each pools delegator counter equals the `Delegators::count()`. // * A pool's `delegator_counter` should always be gt 0. -// +// // - transparent prefx for account ids // Ensure we're `no_std` when compiling for Wasm. diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index fd785306ca8ce..373bc28b5d356 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -1,5 +1,4 @@ // This file is part of Substrate. - // Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 @@ -52,6 +51,8 @@ pub trait WeightInfo { fn withdraw_unbonded_other_kill(s: u32, ) -> Weight; fn create() -> Weight; fn nominate() -> Weight; + fn set_state_other() -> Weight; + fn set_metadata() -> Weight; } /// Weights for pallet_nomination_pools using the Substrate node and recommended hardware. @@ -173,6 +174,18 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(28 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } + + fn set_state_other() -> Weight { + (79_587_000 as Weight) + .saturating_add(T::DbWeight::get().reads(28 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } + fn set_metadata() -> Weight { + (79_587_000 as Weight) + .saturating_add(T::DbWeight::get().reads(28 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } + } // For backwards compatibility and tests @@ -294,4 +307,14 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(28 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } + fn set_state_other() -> Weight { + (79_587_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(28 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } + fn set_metadata() -> Weight { + (79_587_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(28 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } } From b5598a4789abde59d98a1cb38beddcb4e4738368 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 7 Mar 2022 12:45:27 +0100 Subject: [PATCH 167/299] Fix benchmarks --- frame/staking/src/benchmarking.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index ec7d36c820f3e..fa4624f2d63e8 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -806,7 +806,8 @@ benchmarks! { &stash, slash_amount, &mut BalanceOf::::zero(), - &mut NegativeImbalanceOf::::zero() + &mut NegativeImbalanceOf::::zero(), + EraIndex::zero() ); } verify { let balance_after = T::Currency::free_balance(&stash); From f3f1d1b978e021ef8d155395599194617bb7913a Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 7 Mar 2022 12:52:36 +0100 Subject: [PATCH 168/299] Add weight info for new extrinsics --- frame/nomination-pools/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 02cafd7c34699..c5fadf891d7b9 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1437,7 +1437,7 @@ pub mod pallet { Ok(()) } - #[pallet::weight(42)] // TODO: [now] add bench for this + #[pallet::weight(T::WeightInfo::set_state_other())] pub fn set_state_other( origin: OriginFor, pool_account: T::AccountId, @@ -1466,7 +1466,7 @@ pub mod pallet { Ok(()) } - #[pallet::weight(42)] // TODO: [now] add bench for this + #[pallet::weight(T::WeightInfo::set_metadata())] pub fn set_metadata( origin: OriginFor, pool_account: T::AccountId, From 5f53698b174532dfb78f5781071aed0e16a26cc3 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 10 Mar 2022 14:06:41 +0000 Subject: [PATCH 169/299] Some feedback --- bin/node/runtime/src/lib.rs | 1 + frame/nomination-pools/src/lib.rs | 108 +++++++++++++++++------------ frame/nomination-pools/src/mock.rs | 2 + 3 files changed, 67 insertions(+), 44 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 1b7c1d5426e51..230de88a019d3 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -722,6 +722,7 @@ impl pallet_nomination_pools::Config for Runtime { type U256ToBalance = U256ToBalance; type StakingInterface = pallet_staking::Pallet; type PostUnbondingPoolsWindow = PostUnbondPoolsWindow; + type MaxMetadataLen = ConstU32<256>; } parameter_types! { diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index c5fadf891d7b9..930c2d3edbc04 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -79,9 +79,6 @@ //! * Root: can change the nominator, state-toggler, or itself and can perform any of the actions //! the nominator or state-toggler can. //! -//! Note: if it is desired that any of the admin roles are not accessible, they can be set to an -//! anonymous proxy account that has no proxies (and is thus provably keyless). -//! //! ## Design //! //! _Notes_: this section uses pseudo code to explain general design and does not necessarily @@ -333,9 +330,16 @@ pub type SubPoolsWithEra = BoundedBTreeMap, TotalUnbo type RewardPoints = U256; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; + +// TODO: maybe merge these two into an enum const BONDED_ACCOUNT_INDEX: &[u8; 4] = b"bond"; const REWARD_ACCOUNT_INDEX: &[u8; 4] = b"rewd"; +enum AccountType { + Bonded(&[u8]), + Reward(&[u8]), +} + /// Calculate the number of points to issue from a pool as `(current_points / current_balance) * /// new_funds` except for some zero edge cases; see logic and tests for details. fn points_to_issue( @@ -385,6 +389,7 @@ fn balance_to_unbond( #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct Delegator { + // TODO pool_bonded_account. Add in top level docs note about pool always ID'ed by bonded account pub pool: T::AccountId, /// The quantity of points this delegator has in the bonded pool or in a sub pool if /// `Self::unbonding_era` is some. @@ -399,14 +404,19 @@ pub struct Delegator { } /// A pool's possible states. -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, RuntimeDebugNoBound)] -#[cfg_attr(feature = "std", derive(Clone))] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, RuntimeDebugNoBound, Clone)] pub enum PoolState { - Open = 0, - Blocked = 1, - Destroying = 2, + #[codec(index = 0)] + Open, + #[codec(index = 1)] + Blocked, + #[codec(index = 2)] + Destroying, } +// TODO: if this is meant to not be used EVER directly, you can enforce that by putting this and +// `BondedPool` into a private `mod {}` and only exporting the stuff you need. +// TODO: call Inner /// Pool permissions and state #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq)] #[cfg_attr(feature = "std", derive(Clone))] @@ -429,6 +439,8 @@ pub struct BondedPoolStorage { pub state_toggler: T::AccountId, } +// module id + reward/bonded + last 20 chars of depositor account id + #[derive(RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] pub struct BondedPool { @@ -451,11 +463,19 @@ pub struct BondedPool { /// Can toggle the pools state, including setting the pool as blocked or putting the pool into /// destruction mode. The state toggle can also "kick" delegators by unbonding them. state_toggler: T::AccountId, - // /// AccountId of the pool. + /// AccountId of the pool. account: T::AccountId, } impl BondedPool { + // TODO: finish this + pub(crate) fn mutate_checked(update: impl FnOnce() -> R) -> R { + let r = update(); + + // sanity checks + r + } + fn new( depositor: T::AccountId, root: T::AccountId, @@ -576,6 +596,7 @@ impl BondedPool { Ok(()) } + /// Check that the pool can accept a member with `new_funds`. fn ok_to_join_with(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { ensure!(self.state == PoolState::Open, Error::::NotOpen); @@ -584,6 +605,10 @@ impl BondedPool { let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); + + // TODO: this is highly questionable. + // instead of this, where we multiply and it could saturate, watch out for being close to + // the point of saturation. ensure!( new_funds.saturating_add(bonded_balance) < BalanceOf::::max_value().div(10u32.into()), @@ -693,12 +718,14 @@ impl BondedPool { /// Increment the delegator counter. Ensures that the pool and system delegator limits are /// respected. fn inc_delegators(&mut self) -> Result<(), DispatchError> { - if let Some(max_per_pool) = MaxDelegatorsPerPool::::get() { - ensure!(self.delegator_counter < max_per_pool, Error::::MaxDelegators); - } - if let Some(max) = MaxDelegators::::get() { - ensure!(Delegators::::count() < max, Error::::MaxDelegators); - } + ensure!( + MaxDelegatorsPerPool::::get().map_or(true, |max_per_pool| self.delegator_counter < max_per_pool), + Error::::MaxDelegators + ); + ensure!( + MaxDelegatorsPerPool::::get().map_or(true, |max| Delegators::::count() < max), + Error::::MaxDelegators + ); self.delegator_counter = self.delegator_counter.defensive_saturating_add(1); Ok(()) } @@ -1054,8 +1081,7 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Stake funds with a pool. The amount to bond is transferred from the delegator to the - /// pools account and immediately increases the pools bond. If this call is successful the - /// fee is refunded. + /// pools account and immediately increases the pools bond. /// /// # Note /// @@ -1071,6 +1097,9 @@ pub mod pallet { pool_account: T::AccountId, ) -> DispatchResult { let who = ensure_signed(origin)?; + + // TODO: consider merging these checks into ok_to_join_with, also use the same checker + // functions in `fn create()`. ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); // If a delegator already exists that means they already belong to a pool ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); @@ -1080,8 +1109,9 @@ pub mod pallet { bonded_pool.ok_to_join_with(amount)?; bonded_pool.inc_delegators()?; - // We don't actually care about writing the reward pool, we just need its - // total earnings at this point in time. + // TODO: seems like a lot of this and `create` can be factored into a `do_join`. We + // don't actually care about writing the reward pool, we just need its total earnings at + // this point in time. let mut reward_pool = RewardPools::::get(&pool_account) .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; // This is important because we want the most up-to-date total earnings. @@ -1090,6 +1120,8 @@ pub mod pallet { // Transfer the funds to be bonded from `who` to the pools account so the pool can then // go bond them. T::Currency::transfer(&who, &pool_account, amount, ExistenceRequirement::KeepAlive)?; + + // TODO: this can go into one function, similar to `create`. // We must calculate the points to issue *before* we bond `who`'s funds, else the // points:balance ratio will be wrong. let new_points = bonded_pool.issue(amount); @@ -1120,7 +1152,6 @@ pub mod pallet { bonded: amount, }); - // Ok(Pays::No.into()) // TBD Ok(()) } @@ -1377,17 +1408,22 @@ pub mod pallet { // This shouldn't be possible since we are ensured the delegator is not a depositor and // the the account ID is generated based on the accountId ensure!(!BondedPools::::contains_key(&bonded_pool.account), Error::::IdInUse); + // Increase the delegator counter to account for depositor; checks delegator limits. bonded_pool.inc_delegators()?; - // We must calculate the points issued *before* we bond who's funds, else - // points:balance ratio will be wrong. - let points_issued = bonded_pool.issue(amount); + + // TODO: make these one function that does this in the correct order + // We must calculate the points issued *before* we bond who's funds, else points:balance + // ratio will be wrong. + T::Currency::transfer( &who, &bonded_pool.account, amount, ExistenceRequirement::AllowDeath, )?; + let points_issued = bonded_pool.issue(amount); + // Consider making StakingInterface use reference. T::StakingInterface::bond( bonded_pool.account.clone(), // We make the stash and controller the same for simplicity @@ -1518,7 +1554,7 @@ impl Pallet { mut reward_pool: RewardPool, mut delegator: Delegator, ) -> Result<(RewardPool, Delegator, BalanceOf), DispatchError> { - // If the delegator is unbonding they cannot claim rewards. Note that when the delagator + // If the delegator is unbonding they cannot claim rewards. Note that when the delegator // goes to unbond, the unbond function should claim rewards for the final time. ensure!(delegator.unbonding_era.is_none(), Error::::AlreadyUnbonding); @@ -1570,22 +1606,10 @@ impl Pallet { Ok((reward_pool, delegator, delegator_payout)) } - /// Transfer the delegator their payout from the pool and deposit the corresponding event. - fn transfer_reward( - reward_pool: &T::AccountId, - delegator: T::AccountId, - pool: T::AccountId, - payout: BalanceOf, - ) -> Result<(), DispatchError> { - T::Currency::transfer(reward_pool, &delegator, payout, ExistenceRequirement::AllowDeath)?; - Self::deposit_event(Event::::PaidOut { delegator, pool, payout }); - - Ok(()) - } - fn do_reward_payout( + // TODO :doesn't delegator have its id? delegator_id: T::AccountId, - delegator: Delegator, + delegator: Delegator, // TODO: make clear this is mut bonded_pool: &BondedPool, ) -> DispatchResult { let reward_pool = RewardPools::::get(&delegator.pool) @@ -1595,12 +1619,8 @@ impl Pallet { Self::calculate_delegator_payout(bonded_pool, reward_pool, delegator)?; // Transfer payout to the delegator. - Self::transfer_reward( - &reward_pool.account, - delegator_id.clone(), - delegator.pool.clone(), - delegator_payout, - )?; + T::Currency::transfer(reward_pool.account, &delegator_id, payout, ExistenceRequirement::AllowDeath)?; + Self::deposit_event(Event::::PaidOut { delegator: delegator_id.clone(), delegator.pool.clone(), payout }); // Write the updated delegator and reward pool to storage RewardPools::insert(&delegator.pool, reward_pool); diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 72c2a17c925e7..a3ccbeda8fe9b 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -242,6 +242,8 @@ impl ExtBuilder { } } + +// TODO: move this to a pallet function? fn post_checks() { assert_eq!(RewardPools::::count(), BondedPools::::count()); assert!(SubPoolsStorage::::count() <= BondedPools::::count()); From db98c8874da8c27210a81369fcc01c2e6e211f32 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 10 Mar 2022 15:11:50 +0000 Subject: [PATCH 170/299] duo feedback --- frame/nomination-pools/src/lib.rs | 50 +++++++++++++++++-------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 930c2d3edbc04..7d8ece975d3d9 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -331,7 +331,7 @@ type RewardPoints = U256; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; -// TODO: maybe merge these two into an enum +// TODO: maybe merge these two into an enum const BONDED_ACCOUNT_INDEX: &[u8; 4] = b"bond"; const REWARD_ACCOUNT_INDEX: &[u8; 4] = b"rewd"; @@ -470,10 +470,10 @@ pub struct BondedPool { impl BondedPool { // TODO: finish this pub(crate) fn mutate_checked(update: impl FnOnce() -> R) -> R { - let r = update(); + let r = update(); // sanity checks - r + r } fn new( @@ -605,10 +605,10 @@ impl BondedPool { let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); - - // TODO: this is highly questionable. + + // TODO: this is highly questionable. // instead of this, where we multiply and it could saturate, watch out for being close to - // the point of saturation. + // the point of saturation. ensure!( new_funds.saturating_add(bonded_balance) < BalanceOf::::max_value().div(10u32.into()), @@ -1099,7 +1099,7 @@ pub mod pallet { let who = ensure_signed(origin)?; // TODO: consider merging these checks into ok_to_join_with, also use the same checker - // functions in `fn create()`. + // functions in `fn create()`. ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); // If a delegator already exists that means they already belong to a pool ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); @@ -1112,16 +1112,15 @@ pub mod pallet { // TODO: seems like a lot of this and `create` can be factored into a `do_join`. We // don't actually care about writing the reward pool, we just need its total earnings at // this point in time. + // TODO: consider `get_and_update`. let mut reward_pool = RewardPools::::get(&pool_account) .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; - // This is important because we want the most up-to-date total earnings. - reward_pool.update_total_earnings_and_balance(); // Transfer the funds to be bonded from `who` to the pools account so the pool can then // go bond them. T::Currency::transfer(&who, &pool_account, amount, ExistenceRequirement::KeepAlive)?; - // TODO: this can go into one function, similar to `create`. + // TODO: this can go into one function, similar to `create`. // We must calculate the points to issue *before* we bond `who`'s funds, else the // points:balance ratio will be wrong. let new_points = bonded_pool.issue(amount); @@ -1130,6 +1129,9 @@ pub mod pallet { // found, we exit early. T::StakingInterface::bond_extra(pool_account.clone(), amount)?; + // This is important because we want the most up-to-date total earnings. + reward_pool.update_total_earnings_and_balance(); + Delegators::insert( who.clone(), Delegator:: { @@ -1415,7 +1417,7 @@ pub mod pallet { // TODO: make these one function that does this in the correct order // We must calculate the points issued *before* we bond who's funds, else points:balance // ratio will be wrong. - + T::Currency::transfer( &who, &bonded_pool.account, @@ -1554,34 +1556,38 @@ impl Pallet { mut reward_pool: RewardPool, mut delegator: Delegator, ) -> Result<(RewardPool, Delegator, BalanceOf), DispatchError> { + let u256 = |x| T::BalanceToU256::convert(x); // If the delegator is unbonding they cannot claim rewards. Note that when the delegator // goes to unbond, the unbond function should claim rewards for the final time. ensure!(delegator.unbonding_era.is_none(), Error::::AlreadyUnbonding); let last_total_earnings = reward_pool.total_earnings; reward_pool.update_total_earnings_and_balance(); + // Notice there is an edge case where total_earnings have not increased and this is zero - let new_earnings = T::BalanceToU256::convert( + // TODO: make a shorter conversion closure name. + let new_earnings = u256( reward_pool.total_earnings.saturating_sub(last_total_earnings), ); - // The new points that will be added to the pool. For every unit of balance that has - // been earned by the reward pool, we inflate the reward pool points by - // `bonded_pool.points`. In effect this allows each, single unit of balance (e.g. - // plank) to be divvied up pro rata among delegators based on points. - let new_points = T::BalanceToU256::convert(bonded_pool.points).saturating_mul(new_earnings); + // The new points that will be added to the pool. For every unit of balance that has been + // earned by the reward pool, we inflate the reward pool points by `bonded_pool.points`. In + // effect this allows each, single unit of balance (e.g. plank) to be divvied up pro rata + // among delegators based on points. + let new_points = u256(bonded_pool.points).saturating_mul(new_earnings); // The points of the reward pool after taking into account the new earnings. Notice that // this only stays even or increases over time except for when we subtract delegator virtual // shares. let current_points = reward_pool.points.saturating_add(new_points); - // The rewards pool's earnings since the last time this delegator claimed a payout + // The rewards pool's earnings since the last time this delegator claimed a payout. let new_earnings_since_last_claim = reward_pool.total_earnings.saturating_sub(delegator.reward_pool_total_earnings); + // The points of the reward pool that belong to the delegator. - let delegator_virtual_points = T::BalanceToU256::convert(delegator.points) - .saturating_mul(T::BalanceToU256::convert(new_earnings_since_last_claim)); + let delegator_virtual_points = u256(delegator.points) + .saturating_mul(u256(new_earnings_since_last_claim)); let delegator_payout = if delegator_virtual_points.is_zero() || current_points.is_zero() || @@ -1592,7 +1598,7 @@ impl Pallet { // Equivalent to `(delegator_virtual_points / current_points) * reward_pool.balance` T::U256ToBalance::convert( delegator_virtual_points - .saturating_mul(T::BalanceToU256::convert(reward_pool.balance)) + .saturating_mul(u256(reward_pool.balance)) // We check for zero above .div(current_points), ) @@ -1607,7 +1613,7 @@ impl Pallet { } fn do_reward_payout( - // TODO :doesn't delegator have its id? + // TODO :doesn't delegator have its id? delegator_id: T::AccountId, delegator: Delegator, // TODO: make clear this is mut bonded_pool: &BondedPool, From 540baef8f2fdb8fcc27c2d584852144184e23324 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 10 Mar 2022 15:48:28 +0000 Subject: [PATCH 171/299] Incorporate some more feedback --- frame/nomination-pools/src/lib.rs | 74 ++++++++++++++++++++---------- frame/nomination-pools/src/mock.rs | 1 - 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 930c2d3edbc04..ef7e8c9b121d3 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -331,13 +331,22 @@ type RewardPoints = U256; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; -// TODO: maybe merge these two into an enum -const BONDED_ACCOUNT_INDEX: &[u8; 4] = b"bond"; -const REWARD_ACCOUNT_INDEX: &[u8; 4] = b"rewd"; +// // TODO: maybe merge these two into an enum +// const BONDED_ACCOUNT_INDEX: &[u8; 4] = b"bond"; +// const REWARD_ACCOUNT_INDEX: &[u8; 4] = b"rewd"; enum AccountType { - Bonded(&[u8]), - Reward(&[u8]), + Bonded, + Reward, +} + +impl Encode for AccountType { + fn encode(&self) -> Vec { + match self { + Self::Bonded => b"bond".to_vec(), + Self::Reward => b"rewd".to_vec(), + } + } } /// Calculate the number of points to issue from a pool as `(current_points / current_balance) * @@ -389,7 +398,8 @@ fn balance_to_unbond( #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct Delegator { - // TODO pool_bonded_account. Add in top level docs note about pool always ID'ed by bonded account + // TODO pool_bonded_account. Add in top level docs note about pool always ID'ed by bonded + // account pub pool: T::AccountId, /// The quantity of points this delegator has in the bonded pool or in a sub pool if /// `Self::unbonding_era` is some. @@ -470,10 +480,10 @@ pub struct BondedPool { impl BondedPool { // TODO: finish this pub(crate) fn mutate_checked(update: impl FnOnce() -> R) -> R { - let r = update(); + let r = update(); // sanity checks - r + r } fn new( @@ -483,7 +493,7 @@ impl BondedPool { state_toggler: T::AccountId, ) -> Self { Self { - account: Self::create_account(BONDED_ACCOUNT_INDEX, depositor.clone()), + account: Self::create_account(AccountType::Bonded, depositor.clone()), depositor, root, nominator, @@ -531,15 +541,20 @@ impl BondedPool { BondedPools::::remove(self.account); } - fn create_account(index: &[u8; 4], depositor: T::AccountId) -> T::AccountId { + fn create_account(account_type: AccountType, depositor: T::AccountId) -> T::AccountId { // TODO: look into make the prefix transparent by not hashing anything // TODO: look into a using a configurable module id. - let entropy = (b"npls", index, depositor).using_encoded(blake2_256); - Decode::decode(&mut TrailingZeroInput::new(&entropy)).expect("Infinite length input. qed") + let entropy = (b"npls", account_type, depositor).using_encoded(blake2_256); + let a = Decode::decode(&mut TrailingZeroInput::new(&entropy)).expect("Infinite length input. qed"); + + println!("{:?}=create_account out", a); + + a } fn reward_account(&self) -> T::AccountId { - Self::create_account(REWARD_ACCOUNT_INDEX, self.depositor.clone()) + println!("REWARD:"); + Self::create_account(AccountType::Reward, self.depositor.clone()) } /// Get the amount of points to issue for some new funds that will be bonded in the pool. @@ -596,7 +611,6 @@ impl BondedPool { Ok(()) } - /// Check that the pool can accept a member with `new_funds`. fn ok_to_join_with(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { ensure!(self.state == PoolState::Open, Error::::NotOpen); @@ -605,10 +619,10 @@ impl BondedPool { let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); - - // TODO: this is highly questionable. + + // TODO: this is highly questionable. // instead of this, where we multiply and it could saturate, watch out for being close to - // the point of saturation. + // the point of saturation. ensure!( new_funds.saturating_add(bonded_balance) < BalanceOf::::max_value().div(10u32.into()), @@ -719,11 +733,12 @@ impl BondedPool { /// respected. fn inc_delegators(&mut self) -> Result<(), DispatchError> { ensure!( - MaxDelegatorsPerPool::::get().map_or(true, |max_per_pool| self.delegator_counter < max_per_pool), + MaxDelegatorsPerPool::::get() + .map_or(true, |max_per_pool| self.delegator_counter < max_per_pool), Error::::MaxDelegators ); ensure!( - MaxDelegatorsPerPool::::get().map_or(true, |max| Delegators::::count() < max), + MaxDelegators::::get().map_or(true, |max| Delegators::::count() < max), Error::::MaxDelegators ); self.delegator_counter = self.delegator_counter.defensive_saturating_add(1); @@ -1099,7 +1114,7 @@ pub mod pallet { let who = ensure_signed(origin)?; // TODO: consider merging these checks into ok_to_join_with, also use the same checker - // functions in `fn create()`. + // functions in `fn create()`. ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); // If a delegator already exists that means they already belong to a pool ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); @@ -1121,7 +1136,7 @@ pub mod pallet { // go bond them. T::Currency::transfer(&who, &pool_account, amount, ExistenceRequirement::KeepAlive)?; - // TODO: this can go into one function, similar to `create`. + // TODO: this can go into one function, similar to `create`. // We must calculate the points to issue *before* we bond `who`'s funds, else the // points:balance ratio will be wrong. let new_points = bonded_pool.issue(amount); @@ -1415,7 +1430,7 @@ pub mod pallet { // TODO: make these one function that does this in the correct order // We must calculate the points issued *before* we bond who's funds, else points:balance // ratio will be wrong. - + T::Currency::transfer( &who, &bonded_pool.account, @@ -1607,7 +1622,7 @@ impl Pallet { } fn do_reward_payout( - // TODO :doesn't delegator have its id? + // TODO :doesn't delegator have its id? delegator_id: T::AccountId, delegator: Delegator, // TODO: make clear this is mut bonded_pool: &BondedPool, @@ -1619,8 +1634,17 @@ impl Pallet { Self::calculate_delegator_payout(bonded_pool, reward_pool, delegator)?; // Transfer payout to the delegator. - T::Currency::transfer(reward_pool.account, &delegator_id, payout, ExistenceRequirement::AllowDeath)?; - Self::deposit_event(Event::::PaidOut { delegator: delegator_id.clone(), delegator.pool.clone(), payout }); + T::Currency::transfer( + &reward_pool.account, + &delegator_id, + delegator_payout, + ExistenceRequirement::AllowDeath, + )?; + Self::deposit_event(Event::::PaidOut { + delegator: delegator_id.clone(), + pool: delegator.pool.clone(), + payout: delegator_payout, + }); // Write the updated delegator and reward pool to storage RewardPools::insert(&delegator.pool, reward_pool); diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index a3ccbeda8fe9b..e58919fd3bb9d 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -242,7 +242,6 @@ impl ExtBuilder { } } - // TODO: move this to a pallet function? fn post_checks() { assert_eq!(RewardPools::::count(), BondedPools::::count()); From 7c647e5e2f8f78b05144af7b837d617978cd57a0 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 10 Mar 2022 16:46:48 +0000 Subject: [PATCH 172/299] integrate more kian feedback --- frame/nomination-pools/src/lib.rs | 190 ++++++++++++++++----------- frame/nomination-pools/src/mock.rs | 20 +-- frame/nomination-pools/src/sanity.rs | 19 +++ frame/nomination-pools/src/tests.rs | 12 +- 4 files changed, 136 insertions(+), 105 deletions(-) create mode 100644 frame/nomination-pools/src/sanity.rs diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 451e033789d40..d1df47b9cdb2c 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -314,10 +314,14 @@ use sp_io::hashing::blake2_256; use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZeroInput, Zero}; use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div, vec::Vec}; + #[cfg(test)] mod mock; #[cfg(test)] +mod sanity; +#[cfg(test)] mod tests; + pub mod weights; pub use pallet::*; @@ -331,10 +335,6 @@ type RewardPoints = U256; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; -// // TODO: maybe merge these two into an enum -// const BONDED_ACCOUNT_INDEX: &[u8; 4] = b"bond"; -// const REWARD_ACCOUNT_INDEX: &[u8; 4] = b"rewd"; - enum AccountType { Bonded, Reward, @@ -478,14 +478,6 @@ pub struct BondedPool { } impl BondedPool { - // TODO: finish this - pub(crate) fn mutate_checked(update: impl FnOnce() -> R) -> R { - let r = update(); - - // sanity checks - r - } - fn new( depositor: T::AccountId, root: T::AccountId, @@ -549,7 +541,6 @@ impl BondedPool { } fn reward_account(&self) -> T::AccountId { - println!("REWARD:"); Self::create_account(AccountType::Reward, self.depositor.clone()) } @@ -575,6 +566,56 @@ impl BondedPool { points_to_issue } + + /// Increment the delegator counter. Ensures that the pool and system delegator limits are + /// respected. + fn inc_delegators(&mut self) -> Result<(), DispatchError> { + ensure!( + MaxDelegatorsPerPool::::get() + .map_or(true, |max_per_pool| self.delegator_counter < max_per_pool), + Error::::MaxDelegators + ); + ensure!( + MaxDelegators::::get().map_or(true, |max| Delegators::::count() < max), + Error::::MaxDelegators + ); + self.delegator_counter = self.delegator_counter.defensive_saturating_add(1); + Ok(()) + } + + /// Decrement the delegator counter. + fn dec_delegators(mut self) -> Self { + self.delegator_counter = self.delegator_counter.defensive_saturating_sub(1); + self + } + + /// The pools balance that is not locked. This assumes the staking system is the only + fn non_locked_balance(&self) -> BalanceOf { + T::Currency::free_balance(&self.account).saturating_sub( + T::StakingInterface::locked_balance(&self.account).unwrap_or(Zero::zero()), + ) + } + + fn can_nominate(&self, who: &T::AccountId) -> bool { + *who == self.root || *who == self.nominator + } + + fn can_kick(&self, who: &T::AccountId) -> bool { + *who == self.root || *who == self.state_toggler && self.state == PoolState::Blocked + } + + fn can_toggle_state(&self, who: &T::AccountId) -> bool { + *who == self.root || *who == self.state_toggler && !self.is_destroying() + } + + fn can_set_metadata(&self, who: &T::AccountId) -> bool { + *who == self.root || *who == self.state_toggler + } + + fn is_destroying(&self) -> bool { + self.state == PoolState::Destroying + } + /// Whether or not the pool is ok to be in `PoolSate::Open`. If this returns an `Err`, then the /// pool is unrecoverable and should be in the destroying state. fn ok_to_be_open(&self) -> Result<(), DispatchError> { @@ -608,7 +649,18 @@ impl BondedPool { } /// Check that the pool can accept a member with `new_funds`. - fn ok_to_join_with(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { + fn ok_to_join_with( + &self, + new_funds: BalanceOf, + possible_delegator_id: &T::AccountId, + ) -> Result<(), DispatchError> { + // If a delegator already exists that means they already belong to a pool + ensure!( + !Delegators::::contains_key(possible_delegator_id), + Error::::AccountBelongsToOtherPool + ); + ensure!(new_funds >= MinJoinBond::::get(), Error::::MinimumBondNotMet); + ensure!(self.state == PoolState::Open, Error::::NotOpen); self.ok_to_be_open()?; @@ -628,26 +680,6 @@ impl BondedPool { Ok(()) } - fn can_nominate(&self, who: &T::AccountId) -> bool { - *who == self.root || *who == self.nominator - } - - fn can_kick(&self, who: &T::AccountId) -> bool { - *who == self.root || *who == self.state_toggler && self.state == PoolState::Blocked - } - - fn can_toggle_state(&self, who: &T::AccountId) -> bool { - *who == self.root || *who == self.state_toggler && !self.is_destroying() - } - - fn can_set_metadata(&self, who: &T::AccountId) -> bool { - *who == self.root || *who == self.state_toggler - } - - fn is_destroying(&self) -> bool { - self.state == PoolState::Destroying - } - fn ok_to_unbond_other_with( &self, caller: &T::AccountId, @@ -725,33 +757,31 @@ impl BondedPool { } } - /// Increment the delegator counter. Ensures that the pool and system delegator limits are - /// respected. - fn inc_delegators(&mut self) -> Result<(), DispatchError> { - ensure!( - MaxDelegatorsPerPool::::get() - .map_or(true, |max_per_pool| self.delegator_counter < max_per_pool), - Error::::MaxDelegators - ); - ensure!( - MaxDelegators::::get().map_or(true, |max| Delegators::::count() < max), - Error::::MaxDelegators - ); - self.delegator_counter = self.delegator_counter.defensive_saturating_add(1); - Ok(()) - } + fn try_bond_delegator(who: &T::AccountId, amount: BalanceOf) -> DispatchResult { + + // This shouldn't be possible since we are ensured the delegator is not a depositor and + // the the account ID is generated based on the accountId + ensure!(!BondedPools::::contains_key(&bonded_pool.account), Error::::IdInUse); + bonded_pool.ok_to_join_with(amount, &who); + // Increase the delegator counter to account for depositor; checks delegator limits. + bonded_pool.inc_delegators()?; - /// Decrement the delegator counter. - fn dec_delegators(mut self) -> Self { - self.delegator_counter = self.delegator_counter.defensive_saturating_sub(1); - self - } - /// The pools balance that is not locked. This assumes the staking system is the only - fn non_locked_balance(&self) -> BalanceOf { - T::Currency::free_balance(&self.account).saturating_sub( - T::StakingInterface::locked_balance(&self.account).unwrap_or(Zero::zero()), - ) + T::Currency::transfer( + &who, + &self.account, + amount, + ExistenceRequirement::AllowDeath, + )?; + let points_issued = bonded_pool.issue(amount); + // Consider making StakingInterface use reference. + T::StakingInterface::bond( + bonded_pool.account.clone(), + // We make the stash and controller the same for simplicity + bonded_pool.account.clone(), + amount, + bonded_pool.reward_account(), + )?; } } @@ -785,6 +815,14 @@ impl RewardPool { self.total_earnings = new_earnings.saturating_add(self.total_earnings); self.balance = current_balance; } + + /// Get a reward pool and update its total earnings and balance + fn get_and_update(bonded_pool_account: &T::AccountId) -> Option { + RewardPools::::get(bonded_pool_account).map(|mut r| { + r.update_total_earnings_and_balance(); + r + }) + } } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound, RuntimeDebugNoBound)] @@ -1109,22 +1147,21 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - // TODO: consider merging these checks into ok_to_join_with, also use the same checker - // functions in `fn create()`. - ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); - // If a delegator already exists that means they already belong to a pool - ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); + // // TODO: consider merging these checks into ok_to_join_with, also use the same + // checker // functions in `fn create()`. + // ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); + // // If a delegator already exists that means they already belong to a pool + // ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); let mut bonded_pool = BondedPool::::get(&pool_account).ok_or(Error::::PoolNotFound)?; - bonded_pool.ok_to_join_with(amount)?; + bonded_pool.ok_to_join_with(amount, &who)?; bonded_pool.inc_delegators()?; // TODO: seems like a lot of this and `create` can be factored into a `do_join`. We // don't actually care about writing the reward pool, we just need its total earnings at // this point in time. - // TODO: consider `get_and_update`. - let mut reward_pool = RewardPools::::get(&pool_account) + let reward_pool = RewardPool::::get_and_update(&pool_account) .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; // Transfer the funds to be bonded from `who` to the pools account so the pool can then @@ -1140,9 +1177,6 @@ pub mod pallet { // found, we exit early. T::StakingInterface::bond_extra(pool_account.clone(), amount)?; - // This is important because we want the most up-to-date total earnings. - reward_pool.update_total_earnings_and_balance(); - Delegators::insert( who.clone(), Delegator:: { @@ -1412,18 +1446,15 @@ pub mod pallet { amount >= MinCreateBond::::get(), Error::::MinimumBondNotMet ); - if let Some(max_pools) = MaxPools::::get() { - ensure!((BondedPools::::count() as u32) < max_pools, Error::::MaxPools); - } - ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); + ensure!( + MaxPools::::get() + .map_or(true, |max_pools| BondedPools::::count() < max_pools), + Error::::MaxPools + ); let mut bonded_pool = BondedPool::::new(who.clone(), root, nominator, state_toggler); - // This shouldn't be possible since we are ensured the delegator is not a depositor and - // the the account ID is generated based on the accountId - ensure!(!BondedPools::::contains_key(&bonded_pool.account), Error::::IdInUse); - // Increase the delegator counter to account for depositor; checks delegator limits. - bonded_pool.inc_delegators()?; + // TODO: make these one function that does this in the correct order // We must calculate the points issued *before* we bond who's funds, else points:balance @@ -1576,7 +1607,6 @@ impl Pallet { reward_pool.update_total_earnings_and_balance(); // Notice there is an edge case where total_earnings have not increased and this is zero - // TODO: make a shorter conversion closure name. let new_earnings = u256(reward_pool.total_earnings.saturating_sub(last_total_earnings)); // The new points that will be added to the pool. For every unit of balance that has been diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index e58919fd3bb9d..2d74e6464afae 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -231,7 +231,7 @@ impl ExtBuilder { pub fn build_and_execute(self, test: impl FnOnce() -> ()) { self.build().execute_with(|| { test(); - post_checks(); + crate::sanity::checks::(); }) } @@ -242,24 +242,6 @@ impl ExtBuilder { } } -// TODO: move this to a pallet function? -fn post_checks() { - assert_eq!(RewardPools::::count(), BondedPools::::count()); - assert!(SubPoolsStorage::::count() <= BondedPools::::count()); - assert!(Delegators::::count() >= BondedPools::::count()); - bonding_pools_checks(); -} - -fn bonding_pools_checks() { - let mut delegators_seen = 0; - for (_account, pool) in BondedPools::::iter() { - assert!(pool.delegator_counter >= 1); - assert!(pool.delegator_counter <= MaxDelegatorsPerPool::::get().unwrap()); - delegators_seen += pool.delegator_counter; - } - assert_eq!(delegators_seen, Delegators::::count()); -} - pub(crate) fn unsafe_set_state(pool_account: &AccountId, state: PoolState) -> Result<(), ()> { BondedPools::::try_mutate(pool_account, |maybe_bonded_pool| { maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { diff --git a/frame/nomination-pools/src/sanity.rs b/frame/nomination-pools/src/sanity.rs new file mode 100644 index 0000000000000..389065452ed11 --- /dev/null +++ b/frame/nomination-pools/src/sanity.rs @@ -0,0 +1,19 @@ +use super::*; + +/// Sanity check all invariants +pub(crate) fn checks() { + assert_eq!(RewardPools::::count(), BondedPools::::count()); + assert!(SubPoolsStorage::::count() <= BondedPools::::count()); + assert!(Delegators::::count() >= BondedPools::::count()); + bonding_pools_checks::(); +} + +fn bonding_pools_checks() { + let mut delegators_seen = 0; + for (_account, pool) in BondedPools::::iter() { + assert!(pool.delegator_counter >= 1); + assert!(pool.delegator_counter <= MaxDelegatorsPerPool::::get().unwrap()); + delegators_seen += pool.delegator_counter; + } + assert_eq!(delegators_seen, Delegators::::count()); +} diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index b4d36ce0a425b..c13155ad50f90 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -183,25 +183,25 @@ mod bonded_pool { // Simulate a 100% slashed pool StakingMock::set_bonded_balance(123, 0); - assert_noop!(pool.ok_to_join_with(100), Error::::OverflowRisk); + assert_noop!(pool.ok_to_join_with(100, &11), Error::::OverflowRisk); // Simulate a 89% StakingMock::set_bonded_balance(123, 11); - assert_ok!(pool.ok_to_join_with(100)); + assert_ok!(pool.ok_to_join_with(100, &11)); // Simulate a 90% slashed pool StakingMock::set_bonded_balance(123, 10); - assert_noop!(pool.ok_to_join_with(100), Error::::OverflowRisk); + assert_noop!(pool.ok_to_join_with(100, &11), Error::::OverflowRisk); let bonded = 100; StakingMock::set_bonded_balance(123, bonded); // New bonded balance would be over 1/10th of Balance type assert_noop!( - pool.ok_to_join_with(Balance::MAX / 10 - bonded), + pool.ok_to_join_with(Balance::MAX / 10 - bonded, &11), Error::::OverflowRisk ); // and a sanity check - assert_ok!(pool.ok_to_join_with(Balance::MAX / 100 - bonded + 1),); + assert_ok!(pool.ok_to_join_with(Balance::MAX / 100 - bonded + 1, &11)); }); } } @@ -418,7 +418,7 @@ mod join { fn join_errors_correctly() { ExtBuilder::default().build_and_execute_no_checks(|| { assert_noop!( - Pools::join(Origin::signed(10), 420, 420), + Pools::join(Origin::signed(10), 420, PRIMARY_ACCOUNT), Error::::AccountBelongsToOtherPool ); From c7aa8807eade781909948d1dad40a8cb75330751 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 10 Mar 2022 17:36:35 +0000 Subject: [PATCH 173/299] integrate more kian feedback --- frame/nomination-pools/src/lib.rs | 134 +++++++++++++++++----------- frame/nomination-pools/src/tests.rs | 6 +- 2 files changed, 89 insertions(+), 51 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index d1df47b9cdb2c..8ce2cb5b004f4 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -335,8 +335,17 @@ type RewardPoints = U256; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; +/// Extrinsics that bond some funds to the pool. +enum PoolBond { + Create, + Join, +} + +/// Identifier for encoding different pool account types. enum AccountType { + /// The bonded account of the pool. This is functionally both the stash and controller account. Bonded, + /// The reward account of the pool. Reward, } @@ -566,7 +575,6 @@ impl BondedPool { points_to_issue } - /// Increment the delegator counter. Ensures that the pool and system delegator limits are /// respected. fn inc_delegators(&mut self) -> Result<(), DispatchError> { @@ -757,31 +765,50 @@ impl BondedPool { } } - fn try_bond_delegator(who: &T::AccountId, amount: BalanceOf) -> DispatchResult { - - // This shouldn't be possible since we are ensured the delegator is not a depositor and - // the the account ID is generated based on the accountId - ensure!(!BondedPools::::contains_key(&bonded_pool.account), Error::::IdInUse); - bonded_pool.ok_to_join_with(amount, &who); - // Increase the delegator counter to account for depositor; checks delegator limits. - bonded_pool.inc_delegators()?; - - + /// Try to transfer a delegators funds to the bonded pool account and then try to bond them. + /// + /// # Warning + /// + /// This must only be used inside of transactional extrinsic, as funds are transferred prior to + /// attempting a fallible bond. + fn try_bond_delegator( + &mut self, + who: &T::AccountId, + amount: BalanceOf, + ty: PoolBond, + ) -> Result, DispatchError> { + + // Transfer the funds to be bonded from `who` to the pools account so the pool can then + // go bond them. T::Currency::transfer( &who, &self.account, amount, - ExistenceRequirement::AllowDeath, - )?; - let points_issued = bonded_pool.issue(amount); - // Consider making StakingInterface use reference. - T::StakingInterface::bond( - bonded_pool.account.clone(), - // We make the stash and controller the same for simplicity - bonded_pool.account.clone(), - amount, - bonded_pool.reward_account(), + match ty { + PoolBond::Create => ExistenceRequirement::AllowDeath, + PoolBond::Join => ExistenceRequirement::KeepAlive, + }, )?; + // We must calculate the points issued *before* we bond who's funds, else points:balance + // ratio will be wrong. + let points_issued = self.issue(amount); + + match ty { + // TODO: Consider making StakingInterface use reference. + PoolBond::Create => T::StakingInterface::bond( + self.account.clone(), + // We make the stash and controller the same for simplicity + self.account.clone(), + amount, + self.reward_account(), + )?, + // The pool should always be created in such a way its in a state to bond extra, but if + // the active balance is slashed below the minimum bonded or the account cannot be + // found, we exit early. + PoolBond::Join => T::StakingInterface::bond_extra(self.account.clone(), amount)?, + } + + Ok(points_issued) } } @@ -1164,24 +1191,25 @@ pub mod pallet { let reward_pool = RewardPool::::get_and_update(&pool_account) .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; - // Transfer the funds to be bonded from `who` to the pools account so the pool can then - // go bond them. - T::Currency::transfer(&who, &pool_account, amount, ExistenceRequirement::KeepAlive)?; + // // Transfer the funds to be bonded from `who` to the pools account so the pool can then + // // go bond them. + // T::Currency::transfer(&who, &pool_account, amount, ExistenceRequirement::KeepAlive)?; - // TODO: this can go into one function, similar to `create`. - // We must calculate the points to issue *before* we bond `who`'s funds, else the - // points:balance ratio will be wrong. - let new_points = bonded_pool.issue(amount); - // The pool should always be created in such a way its in a state to bond extra, but if - // the active balance is slashed below the minimum bonded or the account cannot be - // found, we exit early. - T::StakingInterface::bond_extra(pool_account.clone(), amount)?; + // // We must calculate the points to issue *before* we bond `who`'s funds, else the + // // points:balance ratio will be wrong. + // let new_points = bonded_pool.issue(amount); + // // The pool should always be created in such a way its in a state to bond extra, but if + // // the active balance is slashed below the minimum bonded or the account cannot be + // // found, we exit early. + // T::StakingInterface::bond_extra(pool_account.clone(), amount)?; + + let points_issued = bonded_pool.try_bond_delegator(&who, amount, PoolBond::Join)?; Delegators::insert( who.clone(), Delegator:: { pool: pool_account.clone(), - points: new_points, + points: points_issued, // At best the reward pool has the rewards up through the previous era. If the // delegator joins prior to the snapshot they will benefit from the rewards of // the active era despite not contributing to the pool's vote weight. If they @@ -1441,6 +1469,7 @@ pub mod pallet { state_toggler: T::AccountId, ) -> DispatchResult { let who = ensure_signed(origin)?; + ensure!( amount >= T::StakingInterface::minimum_bond() && amount >= MinCreateBond::::get(), @@ -1451,30 +1480,35 @@ pub mod pallet { .map_or(true, |max_pools| BondedPools::::count() < max_pools), Error::::MaxPools ); + ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); let mut bonded_pool = BondedPool::::new(who.clone(), root, nominator, state_toggler); + // This shouldn't be possible since we are ensured the delegator is not a depositor and + // the the account ID is generated based on the accountId + ensure!(!BondedPools::::contains_key(&bonded_pool.account), Error::::IdInUse); + bonded_pool.inc_delegators()?; - + let points_issued = + bonded_pool.try_bond_delegator(&who, amount, PoolBond::Create)?; // TODO: make these one function that does this in the correct order // We must calculate the points issued *before* we bond who's funds, else points:balance // ratio will be wrong. - - T::Currency::transfer( - &who, - &bonded_pool.account, - amount, - ExistenceRequirement::AllowDeath, - )?; - let points_issued = bonded_pool.issue(amount); - // Consider making StakingInterface use reference. - T::StakingInterface::bond( - bonded_pool.account.clone(), - // We make the stash and controller the same for simplicity - bonded_pool.account.clone(), - amount, - bonded_pool.reward_account(), - )?; + // T::Currency::transfer( + // &who, + // &bonded_pool.account, + // amount, + // ExistenceRequirement::AllowDeath, + // )?; + // let points_issued = bonded_pool.issue(amount); + // // Consider making StakingInterface use reference. + // T::StakingInterface::bond( + // bonded_pool.account.clone(), + // // We make the stash and controller the same for simplicity + // bonded_pool.account.clone(), + // amount, + // bonded_pool.reward_account(), + // )?; Self::deposit_event(Event::::Created { depositor: who.clone(), diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index c13155ad50f90..34d04dec3c6b4 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -2079,8 +2079,12 @@ mod create { #[test] fn create_errors_correctly() { ExtBuilder::default().build_and_execute_no_checks(|| { + // assert_noop!( + // Pools::create(Origin::signed(10), 420, 123, 456, 789), + // Error::::AccountBelongsToOtherPool + // ); assert_noop!( - Pools::create(Origin::signed(10), 10, 123, 456, 789), + Pools::create(Origin::signed(10), 420, 123, 456, 789), Error::::AccountBelongsToOtherPool ); From 488d38f7c9f5387c50ca9ef5320a4f2fc8eac635 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 10 Mar 2022 18:36:44 +0000 Subject: [PATCH 174/299] More improvements --- frame/nomination-pools/src/lib.rs | 151 ++++++++++------------------ frame/nomination-pools/src/tests.rs | 67 +++++++----- 2 files changed, 91 insertions(+), 127 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 8ce2cb5b004f4..f78689428c80e 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -12,12 +12,13 @@ //! ## Key terms //! //! * bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and -//! [`BondedPoolPoints`] +//! [`BondedPoolPoints`]. Bonded pools are identified via the pools bonded account. //! * reward pool: Tracks rewards earned by actively staked funds. See [`RewardPool`] and -//! [`RewardPools`]. +//! [`RewardPools`]. Reward pools are identified via the pools bonded account. //! * unbonding sub pools: Collection of pools at different phases of the unbonding lifecycle. See -//! [`SubPools`] and [`SubPoolsStorage`]. +//! [`SubPools`] and [`SubPoolsStorage`]. Sub pools are identified via the pools bonded account. //! * delegators: Accounts that are members of pools. See [`Delegator`] and [`Delegators`]. +//! Delegators are identified via their account. //! * point: A measure of a delegators portion of a pools funds. //! //! ## Usage @@ -284,7 +285,8 @@ //! chains total issuance, staking reward rate, and burn rate. // // Invariants -// * A `delegator.pool` must always be a valid entry in `RewardPools`, and `BondedPoolPoints`. +// * A `delegator.bonded_pool_account` must always be a valid entry in `RewardPools`, and +// `BondedPoolPoints`. // * Every entry in `BondedPoolPoints` must have a corresponding entry in `RewardPools` // * If a delegator unbonds, the sub pools should always correctly track slashses such that the // calculated amount when withdrawing unbonded is a lower bound of the pools free balance. @@ -293,9 +295,6 @@ // * The sum of each pools delegator counter equals the `Delegators::count()`. // * A pool's `delegator_counter` should always be gt 0. -// -// - transparent prefx for account ids - // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -407,9 +406,8 @@ fn balance_to_unbond( #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct Delegator { - // TODO pool_bonded_account. Add in top level docs note about pool always ID'ed by bonded - // account - pub pool: T::AccountId, + /// The bonded account of the pool. + pub bonded_pool_account: T::AccountId, /// The quantity of points this delegator has in the bonded pool or in a sub pool if /// `Self::unbonding_era` is some. pub points: BalanceOf, @@ -638,6 +636,8 @@ impl BondedPool { // We checked for zero above .div(bonded_balance); + println!("{:?}=points", self.points); + println!("{:?}=bonded_balance", bonded_balance); // TODO make sure these checks make sense. Taken from staking design chat with Al // Pool points can inflate relative to balance, but only if the pool is slashed. @@ -646,6 +646,8 @@ impl BondedPool { // 90%, ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, + // TODO instead of this, where we multiply and it could saturate, watch out for being close + // to the point of saturation. ensure!( bonded_balance < BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk @@ -657,34 +659,11 @@ impl BondedPool { } /// Check that the pool can accept a member with `new_funds`. - fn ok_to_join_with( - &self, - new_funds: BalanceOf, - possible_delegator_id: &T::AccountId, - ) -> Result<(), DispatchError> { - // If a delegator already exists that means they already belong to a pool - ensure!( - !Delegators::::contains_key(possible_delegator_id), - Error::::AccountBelongsToOtherPool - ); - ensure!(new_funds >= MinJoinBond::::get(), Error::::MinimumBondNotMet); - + fn ok_to_join(&self) -> Result<(), DispatchError> { ensure!(self.state == PoolState::Open, Error::::NotOpen); self.ok_to_be_open()?; - let bonded_balance = - T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); - - // TODO: this is highly questionable. - // instead of this, where we multiply and it could saturate, watch out for being close to - // the point of saturation. - ensure!( - new_funds.saturating_add(bonded_balance) < - BalanceOf::::max_value().div(10u32.into()), - Error::::OverflowRisk - ); - Ok(()) } @@ -766,6 +745,7 @@ impl BondedPool { } /// Try to transfer a delegators funds to the bonded pool account and then try to bond them. + /// Additionally, this increments the delegator counter for the pool. /// /// # Warning /// @@ -777,6 +757,7 @@ impl BondedPool { amount: BalanceOf, ty: PoolBond, ) -> Result, DispatchError> { + self.inc_delegators()?; // Transfer the funds to be bonded from `who` to the pools account so the pool can then // go bond them. @@ -1174,41 +1155,25 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - // // TODO: consider merging these checks into ok_to_join_with, also use the same - // checker // functions in `fn create()`. - // ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); - // // If a delegator already exists that means they already belong to a pool - // ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); + ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); + // If a delegator already exists that means they already belong to a pool + ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); let mut bonded_pool = BondedPool::::get(&pool_account).ok_or(Error::::PoolNotFound)?; - bonded_pool.ok_to_join_with(amount, &who)?; - bonded_pool.inc_delegators()?; + bonded_pool.ok_to_join()?; - // TODO: seems like a lot of this and `create` can be factored into a `do_join`. We // don't actually care about writing the reward pool, we just need its total earnings at // this point in time. let reward_pool = RewardPool::::get_and_update(&pool_account) .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; - // // Transfer the funds to be bonded from `who` to the pools account so the pool can then - // // go bond them. - // T::Currency::transfer(&who, &pool_account, amount, ExistenceRequirement::KeepAlive)?; - - // // We must calculate the points to issue *before* we bond `who`'s funds, else the - // // points:balance ratio will be wrong. - // let new_points = bonded_pool.issue(amount); - // // The pool should always be created in such a way its in a state to bond extra, but if - // // the active balance is slashed below the minimum bonded or the account cannot be - // // found, we exit early. - // T::StakingInterface::bond_extra(pool_account.clone(), amount)?; - let points_issued = bonded_pool.try_bond_delegator(&who, amount, PoolBond::Join)?; Delegators::insert( who.clone(), Delegator:: { - pool: pool_account.clone(), + bonded_pool_account: pool_account.clone(), points: points_issued, // At best the reward pool has the rewards up through the previous era. If the // delegator joins prior to the snapshot they will benefit from the rewards of @@ -1240,7 +1205,7 @@ pub mod pallet { pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; - let bonded_pool = BondedPool::::get(&delegator.pool) + let bonded_pool = BondedPool::::get(&delegator.bonded_pool_account) .defensive_ok_or_else(|| Error::::PoolNotFound)?; Self::do_reward_payout(who, delegator, &bonded_pool)?; @@ -1271,7 +1236,7 @@ pub mod pallet { pub fn unbond_other(origin: OriginFor, target: T::AccountId) -> DispatchResult { let caller = ensure_signed(origin)?; let delegator = Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; - let mut bonded_pool = BondedPool::::get(&delegator.pool) + let mut bonded_pool = BondedPool::::get(&delegator.bonded_pool_account) .defensive_ok_or_else(|| Error::::PoolNotFound)?; bonded_pool.ok_to_unbond_other_with(&caller, &target, &delegator)?; @@ -1284,7 +1249,8 @@ pub mod pallet { let mut delegator = Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; // Note that we lazily create the unbonding pools here if they don't already exist - let sub_pools = SubPoolsStorage::::get(&delegator.pool).unwrap_or_default(); + let sub_pools = + SubPoolsStorage::::get(&delegator.bonded_pool_account).unwrap_or_default(); let current_era = T::StakingInterface::current_era(); let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); @@ -1295,7 +1261,7 @@ pub mod pallet { bonded_pool.points = bonded_pool.points.saturating_sub(delegator.points); // Unbond in the actual underlying pool - T::StakingInterface::unbond(delegator.pool.clone(), balance_to_unbond)?; + T::StakingInterface::unbond(delegator.bonded_pool_account.clone(), balance_to_unbond)?; // Merge any older pools into the general, era agnostic unbond pool. Note that we do // this before inserting to ensure we don't go over the max unbonding pools. @@ -1310,12 +1276,12 @@ pub mod pallet { Self::deposit_event(Event::::Unbonded { delegator: target.clone(), - pool: delegator.pool.clone(), + pool: delegator.bonded_pool_account.clone(), amount: balance_to_unbond, }); // Now that we know everything has worked write the items to storage. bonded_pool.put(); - SubPoolsStorage::insert(&delegator.pool, sub_pools); + SubPoolsStorage::insert(&delegator.bonded_pool_account, sub_pools); Delegators::insert(target, delegator); Ok(()) @@ -1370,16 +1336,19 @@ pub mod pallet { let current_era = T::StakingInterface::current_era(); ensure!(current_era >= unbonding_era, Error::::NotUnbondedYet); - let mut sub_pools = SubPoolsStorage::::get(&delegator.pool) + let mut sub_pools = SubPoolsStorage::::get(&delegator.bonded_pool_account) .defensive_ok_or_else(|| Error::::SubPoolsNotFound)?; - let bonded_pool = BondedPool::::get(&delegator.pool) + let bonded_pool = BondedPool::::get(&delegator.bonded_pool_account) .defensive_ok_or_else(|| Error::::PoolNotFound)?; let should_remove_pool = bonded_pool .ok_to_withdraw_unbonded_other_with(&caller, &target, &delegator, &sub_pools)?; // Before calculate the `balance_to_unbond`, with call withdraw unbonded to ensure the // `non_locked_balance` is correct. - T::StakingInterface::withdraw_unbonded(delegator.pool.clone(), num_slashing_spans)?; + T::StakingInterface::withdraw_unbonded( + delegator.bonded_pool_account.clone(), + num_slashing_spans, + )?; let balance_to_unbond = if let Some(pool) = sub_pools.with_era.get_mut(&unbonding_era) { @@ -1413,7 +1382,7 @@ pub mod pallet { .min(bonded_pool.non_locked_balance()); T::Currency::transfer( - &delegator.pool, + &delegator.bonded_pool_account, &target, balance_to_unbond, ExistenceRequirement::AllowDeath, @@ -1421,15 +1390,17 @@ pub mod pallet { .defensive_map_err(|e| e)?; Self::deposit_event(Event::::Withdrawn { delegator: target.clone(), - pool: delegator.pool.clone(), + pool: delegator.bonded_pool_account.clone(), amount: balance_to_unbond, }); let post_info_weight = if should_remove_pool { - let reward_pool = RewardPools::::take(&delegator.pool) + let reward_pool = RewardPools::::take(&delegator.bonded_pool_account) .defensive_ok_or_else(|| Error::::PoolNotFound)?; - Self::deposit_event(Event::::Destroyed { pool: delegator.pool.clone() }); - SubPoolsStorage::::remove(&delegator.pool); + Self::deposit_event(Event::::Destroyed { + pool: delegator.bonded_pool_account.clone(), + }); + SubPoolsStorage::::remove(&delegator.bonded_pool_account); // Kill accounts from storage by making their balance go below ED. We assume that // the accounts have no references that would prevent destruction once we get to // this point. @@ -1439,7 +1410,7 @@ pub mod pallet { None } else { bonded_pool.dec_delegators().put(); - SubPoolsStorage::::insert(&delegator.pool, sub_pools); + SubPoolsStorage::::insert(&delegator.bonded_pool_account, sub_pools); Some(T::WeightInfo::withdraw_unbonded_other_update(num_slashing_spans)) }; Delegators::::remove(&target); @@ -1471,8 +1442,10 @@ pub mod pallet { let who = ensure_signed(origin)?; ensure!( - amount >= T::StakingInterface::minimum_bond() && - amount >= MinCreateBond::::get(), + amount >= + T::StakingInterface::minimum_bond() + .max(MinCreateBond::::get()) + .max(MinJoinBond::::get()), Error::::MinimumBondNotMet ); ensure!( @@ -1486,29 +1459,8 @@ pub mod pallet { // This shouldn't be possible since we are ensured the delegator is not a depositor and // the the account ID is generated based on the accountId ensure!(!BondedPools::::contains_key(&bonded_pool.account), Error::::IdInUse); - bonded_pool.inc_delegators()?; - - let points_issued = - bonded_pool.try_bond_delegator(&who, amount, PoolBond::Create)?; - - // TODO: make these one function that does this in the correct order - // We must calculate the points issued *before* we bond who's funds, else points:balance - // ratio will be wrong. - // T::Currency::transfer( - // &who, - // &bonded_pool.account, - // amount, - // ExistenceRequirement::AllowDeath, - // )?; - // let points_issued = bonded_pool.issue(amount); - // // Consider making StakingInterface use reference. - // T::StakingInterface::bond( - // bonded_pool.account.clone(), - // // We make the stash and controller the same for simplicity - // bonded_pool.account.clone(), - // amount, - // bonded_pool.reward_account(), - // )?; + + let points_issued = bonded_pool.try_bond_delegator(&who, amount, PoolBond::Create)?; Self::deposit_event(Event::::Created { depositor: who.clone(), @@ -1517,7 +1469,7 @@ pub mod pallet { Delegators::::insert( who, Delegator:: { - pool: bonded_pool.account.clone(), + bonded_pool_account: bonded_pool.account.clone(), points: points_issued, reward_pool_total_earnings: Zero::zero(), unbonding_era: None, @@ -1686,12 +1638,11 @@ impl Pallet { } fn do_reward_payout( - // TODO :doesn't delegator have its id? delegator_id: T::AccountId, delegator: Delegator, // TODO: make clear this is mut bonded_pool: &BondedPool, ) -> DispatchResult { - let reward_pool = RewardPools::::get(&delegator.pool) + let reward_pool = RewardPools::::get(&delegator.bonded_pool_account) .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; let (reward_pool, delegator, delegator_payout) = @@ -1706,12 +1657,12 @@ impl Pallet { )?; Self::deposit_event(Event::::PaidOut { delegator: delegator_id.clone(), - pool: delegator.pool.clone(), + pool: delegator.bonded_pool_account.clone(), payout: delegator_payout, }); // Write the updated delegator and reward pool to storage - RewardPools::insert(&delegator.pool, reward_pool); + RewardPools::insert(&delegator.bonded_pool_account, reward_pool); Delegators::insert(delegator_id, delegator); Ok(()) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 34d04dec3c6b4..f5d7790ff5877 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -47,7 +47,7 @@ fn test_setup_works() { assert_eq!( Delegators::::get(10).unwrap(), Delegator:: { - pool: PRIMARY_ACCOUNT, + bonded_pool_account: PRIMARY_ACCOUNT, points: 10, reward_pool_total_earnings: 0, unbonding_era: None @@ -183,25 +183,22 @@ mod bonded_pool { // Simulate a 100% slashed pool StakingMock::set_bonded_balance(123, 0); - assert_noop!(pool.ok_to_join_with(100, &11), Error::::OverflowRisk); + assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); // Simulate a 89% StakingMock::set_bonded_balance(123, 11); - assert_ok!(pool.ok_to_join_with(100, &11)); + assert_ok!(pool.ok_to_join()); // Simulate a 90% slashed pool StakingMock::set_bonded_balance(123, 10); - assert_noop!(pool.ok_to_join_with(100, &11), Error::::OverflowRisk); + assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); - let bonded = 100; - StakingMock::set_bonded_balance(123, bonded); + StakingMock::set_bonded_balance(123, Balance::MAX / 10); // New bonded balance would be over 1/10th of Balance type - assert_noop!( - pool.ok_to_join_with(Balance::MAX / 10 - bonded, &11), - Error::::OverflowRisk - ); + assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); // and a sanity check - assert_ok!(pool.ok_to_join_with(Balance::MAX / 100 - bonded + 1, &11)); + StakingMock::set_bonded_balance(123, Balance::MAX / 10 - 1); + assert_ok!(pool.ok_to_join()); }); } } @@ -382,7 +379,7 @@ mod join { assert_eq!( Delegators::::get(&11).unwrap(), Delegator:: { - pool: PRIMARY_ACCOUNT, + bonded_pool_account: PRIMARY_ACCOUNT, points: 2, reward_pool_total_earnings: 0, unbonding_era: None @@ -404,7 +401,7 @@ mod join { assert_eq!( Delegators::::get(&12).unwrap(), Delegator:: { - pool: PRIMARY_ACCOUNT, + bonded_pool_account: PRIMARY_ACCOUNT, points: 24, reward_pool_total_earnings: 0, unbonding_era: None @@ -431,6 +428,7 @@ mod join { Error::::OverflowRisk ); + // Given a mocked bonded pool BondedPool:: { depositor: 10, state: PoolState::Open, @@ -442,18 +440,26 @@ mod join { delegator_counter: 1, } .put(); - // Force the points:balance ratio to 100/10 (so 10) + // and reward pool + RewardPools::::insert( + 123, + RewardPool:: { + account: 1123, + balance: Zero::zero(), + total_earnings: Zero::zero(), + points: U256::from(0), + }, + ); + + // Force the points:balance ratio to 100/10 StakingMock::set_bonded_balance(123, 10); assert_noop!(Pools::join(Origin::signed(11), 420, 123), Error::::OverflowRisk); - // Force the points:balance ratio to be a valid 100/100 - StakingMock::set_bonded_balance(123, 100); - // Cumulative balance is > 1/10 of Balance::MAX - assert_noop!( - Pools::join(Origin::signed(11), Balance::MAX / 10 - 100, 123), - Error::::OverflowRisk - ); + StakingMock::set_bonded_balance(123, Balance::MAX / 10); + // Balance is gt 1/10 of Balance::MAX + assert_noop!(Pools::join(Origin::signed(11), 5, 123), Error::::OverflowRisk); + StakingMock::set_bonded_balance(123, 100); // Cannot join a pool that isn't open unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); assert_noop!( @@ -540,7 +546,12 @@ mod claim_payout { use super::*; fn del(points: Balance, reward_pool_total_earnings: Balance) -> Delegator { - Delegator { pool: PRIMARY_ACCOUNT, points, reward_pool_total_earnings, unbonding_era: None } + Delegator { + bonded_pool_account: PRIMARY_ACCOUNT, + points, + reward_pool_total_earnings, + unbonding_era: None, + } } fn rew(balance: Balance, points: u32, total_earnings: Balance) -> RewardPool { @@ -1495,7 +1506,7 @@ mod unbond { // Add the delegator let delegator = Delegator { - pool: 1, + bonded_pool_account: 1, points: 10, reward_pool_total_earnings: 0, unbonding_era: None, @@ -1511,7 +1522,7 @@ mod unbond { fn unbond_panics_when_reward_pool_not_found() { ExtBuilder::default().build_and_execute(|| { let delegator = Delegator { - pool: 1, + bonded_pool_account: 1, points: 10, reward_pool_total_earnings: 0, unbonding_era: None, @@ -1761,7 +1772,7 @@ mod withdraw_unbonded_other { ); let mut delegator = Delegator { - pool: PRIMARY_ACCOUNT, + bonded_pool_account: PRIMARY_ACCOUNT, points: 10, reward_pool_total_earnings: 0, unbonding_era: None, @@ -2044,7 +2055,7 @@ mod create { assert_eq!( Delegators::::get(11).unwrap(), Delegator { - pool: stash, + bonded_pool_account: stash, points: StakingMock::minimum_bond(), reward_pool_total_earnings: Zero::zero(), unbonding_era: None @@ -2183,8 +2194,10 @@ mod set_state_other { #[test] fn set_state_other_works() { ExtBuilder::default().build_and_execute(|| { - // Only the root and state toggler can change the state when the pool is ok to be open. + // Given assert_ok!(BondedPool::::get(&PRIMARY_ACCOUNT).unwrap().ok_to_be_open()); + + // Only the root and state toggler can change the state when the pool is ok to be open. assert_noop!( Pools::set_state_other(Origin::signed(10), PRIMARY_ACCOUNT, PoolState::Blocked), Error::::CanNotChangeState From 54abbd684f0a3b9743726bfb8454fb4bbd9ca305 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 11 Mar 2022 12:36:53 +0000 Subject: [PATCH 175/299] Add destroying_mul --- frame/nomination-pools/src/lib.rs | 45 ++++++++++++++++++------ frame/nomination-pools/src/tests.rs | 54 ++++++++++++++--------------- 2 files changed, 61 insertions(+), 38 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index f78689428c80e..0b163217932f2 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -791,6 +791,24 @@ impl BondedPool { Ok(points_issued) } + + /// If the the product of `a` and `b` saturates type `U`, then the pool is set to destroying and + /// the upper bound of `U` is returned. + /// + /// # Note + /// + /// This only performs in memory modifications. The pool should be written to storage if its + /// state is set to destroying. + fn destroying_mul(&mut self, a: U, b: U) -> U + where U: U: Saturating + CheckedMul + Bounded, + { + if let Some(res) = a.check_mul(b) { + res + } else { + self.state = PoolState::Destroying; + U::max_value() + } + } } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] @@ -1208,7 +1226,7 @@ pub mod pallet { let bonded_pool = BondedPool::::get(&delegator.bonded_pool_account) .defensive_ok_or_else(|| Error::::PoolNotFound)?; - Self::do_reward_payout(who, delegator, &bonded_pool)?; + Self::do_reward_payout(who, delegator, bonded_pool)?; Ok(()) } @@ -1580,9 +1598,9 @@ pub mod pallet { impl Pallet { /// Calculate the rewards for `delegator`. fn calculate_delegator_payout( - bonded_pool: &BondedPool, - mut reward_pool: RewardPool, - mut delegator: Delegator, + bonded_pool: &mut BondedPool, + reward_pool: &mut RewardPool, + delegator: &mut Delegator, ) -> Result<(RewardPool, Delegator, BalanceOf), DispatchError> { let u256 = |x| T::BalanceToU256::convert(x); // If the delegator is unbonding they cannot claim rewards. Note that when the delegator @@ -1599,7 +1617,7 @@ impl Pallet { // earned by the reward pool, we inflate the reward pool points by `bonded_pool.points`. In // effect this allows each, single unit of balance (e.g. plank) to be divvied up pro rata // among delegators based on points. - let new_points = u256(bonded_pool.points).saturating_mul(new_earnings); + let new_points = bonded_pool.destroying_mul(u256(bonded_pool.points), new_earnings); // The points of the reward pool after taking into account the new earnings. Notice that // this only stays even or increases over time except for when we subtract delegator virtual @@ -1612,7 +1630,7 @@ impl Pallet { // The points of the reward pool that belong to the delegator. let delegator_virtual_points = - u256(delegator.points).saturating_mul(u256(new_earnings_since_last_claim)); + bonded_pool.destroying_mul(u256(delegator.points), u256(new_earnings_since_last_claim)); let delegator_payout = if delegator_virtual_points.is_zero() || current_points.is_zero() || @@ -1622,8 +1640,7 @@ impl Pallet { } else { // Equivalent to `(delegator_virtual_points / current_points) * reward_pool.balance` T::U256ToBalance::convert( - delegator_virtual_points - .saturating_mul(u256(reward_pool.balance)) + bonded_pool.destroying_mul(delegator_virtual_points, u256(reward_pool.balance)) // We check for zero above .div(current_points), ) @@ -1634,18 +1651,19 @@ impl Pallet { reward_pool.points = current_points.saturating_sub(delegator_virtual_points); reward_pool.balance = reward_pool.balance.saturating_sub(delegator_payout); - Ok((reward_pool, delegator, delegator_payout)) + Ok(delegator_payout) } fn do_reward_payout( delegator_id: T::AccountId, delegator: Delegator, // TODO: make clear this is mut - bonded_pool: &BondedPool, + bonded_pool: BondedPool, ) -> DispatchResult { + let was_destroying = bonded_pool.is_destroying(); let reward_pool = RewardPools::::get(&delegator.bonded_pool_account) .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; - let (reward_pool, delegator, delegator_payout) = + let delegator_payout = Self::calculate_delegator_payout(bonded_pool, reward_pool, delegator)?; // Transfer payout to the delegator. @@ -1664,6 +1682,11 @@ impl Pallet { // Write the updated delegator and reward pool to storage RewardPools::insert(&delegator.bonded_pool_account, reward_pool); Delegators::insert(delegator_id, delegator); + if bonded_pool.is_destroying() && !was_destroying { + // Event that pool was state was changed + bonded_pool.put() + } + Ok(()) } diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index f5d7790ff5877..9cc30a1072b39 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -774,7 +774,7 @@ mod claim_payout { // Given no rewards have been earned // When - let (reward_pool, delegator, payout) = + let payout = Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); // Then @@ -786,7 +786,7 @@ mod claim_payout { Balances::make_free_balance_be(&reward_pool.account, 5); // When - let (reward_pool, delegator, payout) = + let payout = Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); // Then @@ -798,8 +798,8 @@ mod claim_payout { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 10); // When - let (reward_pool, delegator, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); + let payout = + Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &delegator).unwrap(); // Then assert_eq!(payout, 10); // (10 * 10 del virtual points / 10 pool points) * 5 pool balance @@ -810,7 +810,7 @@ mod claim_payout { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 0); // When - let (reward_pool, delegator, payout) = + let payout = Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); // Then @@ -841,8 +841,8 @@ mod claim_payout { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 100); // When - let (reward_pool, del_10, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_10).unwrap(); + let payout = + Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_10).unwrap(); // Then assert_eq!(payout, 10); // (10 del virtual points / 100 pool points) * 100 pool balance @@ -852,8 +852,8 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 10)); // When - let (reward_pool, del_40, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_40).unwrap(); + let payout = + Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_40).unwrap(); // Then assert_eq!(payout, 40); // (400 del virtual points / 900 pool points) * 90 pool balance @@ -871,8 +871,8 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 40)); // When - let (reward_pool, del_50, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_50).unwrap(); + let payout = + Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_50).unwrap(); // Then assert_eq!(payout, 50); // (50 del virtual points / 50 pool points) * 50 pool balance @@ -885,8 +885,8 @@ mod claim_payout { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 50); // When - let (reward_pool, del_10, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_10).unwrap(); + let payout = + Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_10).unwrap(); // Then assert_eq!(payout, 5); // (500 del virtual points / 5,000 pool points) * 50 pool balance @@ -896,8 +896,8 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 5)); // When - let (reward_pool, del_40, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_40).unwrap(); + let payout = + Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_40).unwrap(); // Then assert_eq!(payout, 20); // (2,000 del virtual points / 4,500 pool points) * 45 pool balance @@ -910,8 +910,8 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 75); // When - let (reward_pool, del_50, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_50).unwrap(); + let payout = + Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_50).unwrap(); // Then assert_eq!(payout, 50); // (5,000 del virtual points / 7,5000 pool points) * 75 pool balance @@ -932,8 +932,8 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 50)); // When - let (reward_pool, del_10, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_10).unwrap(); + let payout = + Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_10).unwrap(); // Then assert_eq!(payout, 5); @@ -947,8 +947,8 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 420); // When - let (reward_pool, del_10, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_10).unwrap(); + let payout = + Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_10).unwrap(); // Then assert_eq!(payout, 40); @@ -973,8 +973,8 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 400); // When - let (reward_pool, del_10, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_10).unwrap(); + let payout = + Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_10).unwrap(); // Then assert_eq!(payout, 2); // (200 del virtual points / 38,000 pool points) * 400 pool balance @@ -984,8 +984,8 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 2)); // When - let (reward_pool, del_40, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_40).unwrap(); + let payout = + Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_40).unwrap(); // Then assert_eq!(payout, 188); // (18,800 del virtual points / 39,800 pool points) * 399 pool balance @@ -995,8 +995,8 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 188)); // When - let (reward_pool, del_50, payout) = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, del_50).unwrap(); + let payout = + Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_50).unwrap(); // Then assert_eq!(payout, 210); // (21,000 / 21,000) * 210 From 5068c6922eb9a28fdfc290ffb3b8db29c80ccf91 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 11 Mar 2022 15:24:15 +0000 Subject: [PATCH 176/299] Make do_reward_payout take refs --- frame/nomination-pools/src/lib.rs | 116 +++++++++------ frame/nomination-pools/src/tests.rs | 215 +++++++++++++++++++--------- 2 files changed, 222 insertions(+), 109 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 0b163217932f2..834cc0e4553c9 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -792,22 +792,14 @@ impl BondedPool { Ok(points_issued) } - /// If the the product of `a` and `b` saturates type `U`, then the pool is set to destroying and - /// the upper bound of `U` is returned. - /// - /// # Note - /// - /// This only performs in memory modifications. The pool should be written to storage if its - /// state is set to destroying. - fn destroying_mul(&mut self, a: U, b: U) -> U - where U: U: Saturating + CheckedMul + Bounded, - { - if let Some(res) = a.check_mul(b) { - res - } else { - self.state = PoolState::Destroying; - U::max_value() + /// If `n` saturates at it's upper bound, mark the pool as destroying. This is useful when a + /// number saturating indicates the pool can no longer correctly keep track of state. + fn bound_check(&mut self, n: U256) -> U256 { + if n == U256::max_value() { + self.state = PoolState::Destroying } + + n } } @@ -970,10 +962,10 @@ pub mod pallet { /// The nominating balance. type Currency: Currency; - // Infallible method for converting `Currency::Balance` to `U256`. + /// Infallible method for converting `Currency::Balance` to `U256`. type BalanceToU256: Convert, U256>; - // Infallible method for converting `U256` to `Currency::Balance`. + /// Infallible method for converting `U256` to `Currency::Balance`. type U256ToBalance: Convert>; /// The interface for nominating. @@ -1098,6 +1090,7 @@ pub mod pallet { Unbonded { delegator: T::AccountId, pool: T::AccountId, amount: BalanceOf }, Withdrawn { delegator: T::AccountId, pool: T::AccountId, amount: BalanceOf }, Destroyed { pool: T::AccountId }, + State { pool: T::AccountId, new_state: PoolState }, } #[pallet::error] @@ -1222,11 +1215,21 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::claim_payout())] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; - let bonded_pool = BondedPool::::get(&delegator.bonded_pool_account) + let mut delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let mut bonded_pool = BondedPool::::get(&delegator.bonded_pool_account) .defensive_ok_or_else(|| Error::::PoolNotFound)?; + let was_destroying = bonded_pool.is_destroying(); - Self::do_reward_payout(who, delegator, bonded_pool)?; + Self::do_reward_payout(who.clone(), &mut delegator, &mut bonded_pool)?; + + if bonded_pool.is_destroying() && !was_destroying { + Self::deposit_event(Event::::State { + pool: bonded_pool.account.clone(), + new_state: PoolState::Destroying, + }); + } + bonded_pool.put(); + Delegators::insert(who, delegator); Ok(()) } @@ -1253,19 +1256,27 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::unbond_other())] pub fn unbond_other(origin: OriginFor, target: T::AccountId) -> DispatchResult { let caller = ensure_signed(origin)?; - let delegator = Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; + let mut delegator = + Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; let mut bonded_pool = BondedPool::::get(&delegator.bonded_pool_account) .defensive_ok_or_else(|| Error::::PoolNotFound)?; bonded_pool.ok_to_unbond_other_with(&caller, &target, &delegator)?; + let was_destroying = bonded_pool.is_destroying(); + // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the bonded pool and thus they can no longer claim their payouts. // It is not strictly necessary to claim the rewards, but we do it here for UX. - Self::do_reward_payout(target.clone(), delegator, &bonded_pool)?; + Self::do_reward_payout(target.clone(), &mut delegator, &mut bonded_pool)?; + + // TODO: make do_reward_payout pass through delegator and bonded_pool + // // Re-fetch the delegator because they where updated by `do_reward_payout`. + // let mut delegator = + // Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; + // // Re-fetch the bonded pool because they where updated by `do_reward_payout`. + // let mut bonded_pool = BondedPool::::get(&delegator.bonded_pool_account) + // .defensive_ok_or_else(|| Error::::PoolNotFound)?; - // Re-fetch the delegator because they where updated by `do_reward_payout`. - let mut delegator = - Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPoolsStorage::::get(&delegator.bonded_pool_account).unwrap_or_default(); @@ -1297,6 +1308,13 @@ pub mod pallet { pool: delegator.bonded_pool_account.clone(), amount: balance_to_unbond, }); + if bonded_pool.is_destroying() && !was_destroying { + // Persist the pool to storage iff the state was updated + Self::deposit_event(Event::::State { + pool: bonded_pool.account.clone(), + new_state: PoolState::Destroying, + }); + } // Now that we know everything has worked write the items to storage. bonded_pool.put(); SubPoolsStorage::insert(&delegator.bonded_pool_account, sub_pools); @@ -1545,6 +1563,11 @@ pub mod pallet { Err(Error::::CanNotChangeState)?; } + Self::deposit_event(Event::::State { + pool: bonded_pool.account.clone(), + new_state: bonded_pool.state.clone(), + }); + bonded_pool.put(); Ok(()) @@ -1601,8 +1624,9 @@ impl Pallet { bonded_pool: &mut BondedPool, reward_pool: &mut RewardPool, delegator: &mut Delegator, - ) -> Result<(RewardPool, Delegator, BalanceOf), DispatchError> { + ) -> Result, DispatchError> { let u256 = |x| T::BalanceToU256::convert(x); + let balance = |x| T::U256ToBalance::convert(x); // If the delegator is unbonding they cannot claim rewards. Note that when the delegator // goes to unbond, the unbond function should claim rewards for the final time. ensure!(delegator.unbonding_era.is_none(), Error::::AlreadyUnbonding); @@ -1617,12 +1641,12 @@ impl Pallet { // earned by the reward pool, we inflate the reward pool points by `bonded_pool.points`. In // effect this allows each, single unit of balance (e.g. plank) to be divvied up pro rata // among delegators based on points. - let new_points = bonded_pool.destroying_mul(u256(bonded_pool.points), new_earnings); + let new_points = u256(bonded_pool.points).saturating_mul(new_earnings); // The points of the reward pool after taking into account the new earnings. Notice that // this only stays even or increases over time except for when we subtract delegator virtual // shares. - let current_points = reward_pool.points.saturating_add(new_points); + let current_points = bonded_pool.bound_check(reward_pool.points.saturating_add(new_points)); // The rewards pool's earnings since the last time this delegator claimed a payout. let new_earnings_since_last_claim = @@ -1630,7 +1654,7 @@ impl Pallet { // The points of the reward pool that belong to the delegator. let delegator_virtual_points = - bonded_pool.destroying_mul(u256(delegator.points), u256(new_earnings_since_last_claim)); + u256(delegator.points).saturating_mul(u256(new_earnings_since_last_claim)); let delegator_payout = if delegator_virtual_points.is_zero() || current_points.is_zero() || @@ -1639,14 +1663,21 @@ impl Pallet { Zero::zero() } else { // Equivalent to `(delegator_virtual_points / current_points) * reward_pool.balance` - T::U256ToBalance::convert( - bonded_pool.destroying_mul(delegator_virtual_points, u256(reward_pool.balance)) + let numerator = { + let numerator = delegator_virtual_points.saturating_mul(u256(reward_pool.balance)); + bonded_pool.bound_check(numerator) + }; + balance( + numerator // We check for zero above .div(current_points), ) }; // Record updates + if reward_pool.total_earnings == BalanceOf::::max_value() { + bonded_pool.state = PoolState::Destroying; + } delegator.reward_pool_total_earnings = reward_pool.total_earnings; reward_pool.points = current_points.saturating_sub(delegator_virtual_points); reward_pool.balance = reward_pool.balance.saturating_sub(delegator_payout); @@ -1654,17 +1685,22 @@ impl Pallet { Ok(delegator_payout) } + /// If the delegator has some rewards, transfer a payout from the reward pool to the delegator. + /// + /// # Note + /// + /// This will persist updates for the reward pool to storage. But it will *not* persist updates + /// to the `delegator` or `bonded_pool` to storage, that is the responsibility of the caller. fn do_reward_payout( delegator_id: T::AccountId, - delegator: Delegator, // TODO: make clear this is mut - bonded_pool: BondedPool, + delegator: &mut Delegator, + bonded_pool: &mut BondedPool, ) -> DispatchResult { - let was_destroying = bonded_pool.is_destroying(); - let reward_pool = RewardPools::::get(&delegator.bonded_pool_account) + let mut reward_pool = RewardPools::::get(&delegator.bonded_pool_account) .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; let delegator_payout = - Self::calculate_delegator_payout(bonded_pool, reward_pool, delegator)?; + Self::calculate_delegator_payout(bonded_pool, &mut reward_pool, delegator)?; // Transfer payout to the delegator. T::Currency::transfer( @@ -1679,14 +1715,8 @@ impl Pallet { payout: delegator_payout, }); - // Write the updated delegator and reward pool to storage + // Write the reward pool to storage RewardPools::insert(&delegator.bonded_pool_account, reward_pool); - Delegators::insert(delegator_id, delegator); - if bonded_pool.is_destroying() && !was_destroying { - // Event that pool was state was changed - bonded_pool.put() - } - Ok(()) } diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 9cc30a1072b39..6d32110985cc0 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -748,16 +748,35 @@ mod claim_payout { }); } + // TODO + // #[test] + // fn calculate_delegator_payout_sets_pool_state_to_destroying_if_mul_saturates() { + // ExtBuilder::default().build_and_execute(|| { + // let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + // let reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); + // let delegator = Delegators::::get(10).unwrap(); + + // let u64_max_u128 = u64::MAX as u128; + // assert!(u64_max_u128.check_mul(u64_max_u128).is_none()); + // Balances::make_free_balance_be(&reward_pool.account, u64::MAX as u128); + // // let reward_pool = + // }); + // } + #[test] fn calculate_delegator_payout_errors_if_a_delegator_is_unbonding() { ExtBuilder::default().build_and_execute(|| { - let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); - let reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); delegator.unbonding_era = Some(0 + 3); assert_noop!( - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator), + Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut delegator + ), Error::::AlreadyUnbonding ); }); @@ -768,14 +787,18 @@ mod claim_payout { let del = |reward_pool_total_earnings| del(10, reward_pool_total_earnings); ExtBuilder::default().build_and_execute(|| { - let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); - let reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); - let delegator = Delegators::::get(10).unwrap(); + let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut delegator = Delegators::::get(10).unwrap(); // Given no rewards have been earned // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut delegator, + ) + .unwrap(); // Then assert_eq!(payout, 0); @@ -786,8 +809,12 @@ mod claim_payout { Balances::make_free_balance_be(&reward_pool.account, 5); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut delegator, + ) + .unwrap(); // Then assert_eq!(payout, 5); // (10 * 5 del virtual points / 10 * 5 pool points) * 5 pool balance @@ -798,8 +825,12 @@ mod claim_payout { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 10); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &delegator).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut delegator, + ) + .unwrap(); // Then assert_eq!(payout, 10); // (10 * 10 del virtual points / 10 pool points) * 5 pool balance @@ -810,8 +841,12 @@ mod claim_payout { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 0); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, reward_pool, delegator).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut delegator, + ) + .unwrap(); // Then assert_eq!(payout, 0); @@ -825,14 +860,14 @@ mod claim_payout { ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); - let reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); // Delegator with 10 points - let del_10 = Delegators::::get(10).unwrap(); + let mut del_10 = Delegators::::get(10).unwrap(); // Delegator with 40 points - let del_40 = Delegators::::get(40).unwrap(); + let mut del_40 = Delegators::::get(40).unwrap(); // Delegator with 50 points - let del_50 = Delegators::::get(50).unwrap(); + let mut del_50 = Delegators::::get(50).unwrap(); // Given we have a total of 100 points split among the delegators assert_eq!(del_50.points + del_40.points + del_10.points, 100); @@ -841,8 +876,12 @@ mod claim_payout { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 100); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_10).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut del_10, + ) + .unwrap(); // Then assert_eq!(payout, 10); // (10 del virtual points / 100 pool points) * 100 pool balance @@ -852,8 +891,12 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 10)); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_40).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut del_40, + ) + .unwrap(); // Then assert_eq!(payout, 40); // (400 del virtual points / 900 pool points) * 90 pool balance @@ -871,8 +914,12 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 40)); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_50).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut del_50, + ) + .unwrap(); // Then assert_eq!(payout, 50); // (50 del virtual points / 50 pool points) * 50 pool balance @@ -885,8 +932,12 @@ mod claim_payout { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 50); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_10).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut del_10, + ) + .unwrap(); // Then assert_eq!(payout, 5); // (500 del virtual points / 5,000 pool points) * 50 pool balance @@ -896,8 +947,12 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 5)); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_40).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut del_40, + ) + .unwrap(); // Then assert_eq!(payout, 20); // (2,000 del virtual points / 4,500 pool points) * 45 pool balance @@ -910,8 +965,12 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 75); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_50).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut del_50, + ) + .unwrap(); // Then assert_eq!(payout, 50); // (5,000 del virtual points / 7,5000 pool points) * 75 pool balance @@ -932,8 +991,12 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 50)); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_10).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut del_10, + ) + .unwrap(); // Then assert_eq!(payout, 5); @@ -947,8 +1010,12 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 420); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_10).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut del_10, + ) + .unwrap(); // Then assert_eq!(payout, 40); @@ -973,8 +1040,12 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 400); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_10).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut del_10, + ) + .unwrap(); // Then assert_eq!(payout, 2); // (200 del virtual points / 38,000 pool points) * 400 pool balance @@ -984,8 +1055,12 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 2)); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_40).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut del_40, + ) + .unwrap(); // Then assert_eq!(payout, 188); // (18,800 del virtual points / 39,800 pool points) * 399 pool balance @@ -995,8 +1070,12 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 188)); // When - let payout = - Pools::calculate_delegator_payout(&bonded_pool, &reward_pool, &del_50).unwrap(); + let payout = Pools::calculate_delegator_payout( + &mut bonded_pool, + &mut reward_pool, + &mut del_50, + ) + .unwrap(); // Then assert_eq!(payout, 210); // (21,000 / 21,000) * 210 @@ -1010,7 +1089,7 @@ mod claim_payout { ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); // Given the bonded pool has 100 points assert_eq!(bonded_pool.points, 100); @@ -1021,13 +1100,17 @@ mod claim_payout { // and the reward pool has earned 100 in rewards Balances::make_free_balance_be(&REWARDS_ACCOUNT, 100); + let mut del_10 = Delegators::get(10).unwrap(); + let mut del_40 = Delegators::get(40).unwrap(); + let mut del_50 = Delegators::get(50).unwrap(); + // When - assert_ok!(Pools::do_reward_payout(10, Delegators::get(10).unwrap(), &bonded_pool)); + assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); // Then // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool // balance - assert_eq!(Delegators::::get(10).unwrap(), del(10, 100)); + assert_eq!(del_10, del(10, 100)); assert_eq!( RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(90, 100 * 100 - 100 * 10, 100) @@ -1036,11 +1119,11 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 90); // When - assert_ok!(Pools::do_reward_payout(40, Delegators::get(40).unwrap(), &bonded_pool)); + assert_ok!(Pools::do_reward_payout(40, &mut del_40, &mut bonded_pool)); // Then // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance - assert_eq!(Delegators::::get(40).unwrap(), del(40, 100)); + assert_eq!(del_40, del(40, 100)); assert_eq!( RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(50, 9_000 - 100 * 40, 100) @@ -1049,11 +1132,11 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 50); // When - assert_ok!(Pools::do_reward_payout(50, Delegators::get(50).unwrap(), &bonded_pool)); + assert_ok!(Pools::do_reward_payout(50, &mut del_50, &mut bonded_pool)); // Then // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance - assert_eq!(Delegators::::get(50).unwrap(), del(50, 100)); + assert_eq!(del_50, del(50, 100)); assert_eq!(RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(0, 0, 100)); assert_eq!(Balances::free_balance(&50), 50); assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); @@ -1062,11 +1145,11 @@ mod claim_payout { Balances::make_free_balance_be(&REWARDS_ACCOUNT, 50); // When - assert_ok!(Pools::do_reward_payout(10, Delegators::get(10).unwrap(), &bonded_pool)); + assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); // Then // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance - assert_eq!(Delegators::::get(10).unwrap(), del(10, 150)); + assert_eq!(del_10, del(10, 150)); assert_eq!( RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(45, 5_000 - 50 * 10, 150) @@ -1075,12 +1158,12 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 45); // When - assert_ok!(Pools::do_reward_payout(40, Delegators::get(40).unwrap(), &bonded_pool)); + assert_ok!(Pools::do_reward_payout(40, &mut del_40, &mut bonded_pool)); // Then // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool // balance - assert_eq!(Delegators::::get(40).unwrap(), del(40, 150)); + assert_eq!(del_40, del(40, 150)); assert_eq!( RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(25, 4_500 - 50 * 40, 150) @@ -1093,12 +1176,12 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 75); // When - assert_ok!(Pools::do_reward_payout(50, Delegators::get(50).unwrap(), &bonded_pool)); + assert_ok!(Pools::do_reward_payout(50, &mut del_50, &mut bonded_pool)); // Then // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 // pool balance - assert_eq!(Delegators::::get(50).unwrap(), del(50, 200)); + assert_eq!(del_50, del(50, 200)); assert_eq!( RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew( @@ -1115,11 +1198,11 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 25); // When - assert_ok!(Pools::do_reward_payout(10, Delegators::get(10).unwrap(), &bonded_pool)); + assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); // Then // We expect a payout of 5 - assert_eq!(Delegators::::get(10).unwrap(), del(10, 200)); + assert_eq!(del_10, del(10, 200)); assert_eq!( RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(20, 2_500 - 10 * 50, 200) @@ -1132,11 +1215,11 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 420); // When - assert_ok!(Pools::do_reward_payout(10, Delegators::get(10).unwrap(), &bonded_pool)); + assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); // Then // We expect a payout of 40 - assert_eq!(Delegators::::get(10).unwrap(), del(10, 600)); + assert_eq!(del_10, del(10, 600)); assert_eq!( RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew( @@ -1157,12 +1240,12 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 400); // When - assert_ok!(Pools::do_reward_payout(10, Delegators::get(10).unwrap(), &bonded_pool)); + assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); // Then // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool // balance - assert_eq!(Delegators::::get(10).unwrap(), del(10, 620)); + assert_eq!(del_10, del(10, 620)); assert_eq!( RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(398, (38_000 + 20 * 100) - 10 * 20, 620) @@ -1171,12 +1254,12 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 398); // When - assert_ok!(Pools::do_reward_payout(40, Delegators::get(40).unwrap(), &bonded_pool)); + assert_ok!(Pools::do_reward_payout(40, &mut del_40, &mut bonded_pool)); // Then // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 // pool balance - assert_eq!(Delegators::::get(40).unwrap(), del(40, 620)); + assert_eq!(del_40, del(40, 620)); assert_eq!( RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(210, 39_800 - 40 * 470, 620) @@ -1185,11 +1268,11 @@ mod claim_payout { assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 210); // When - assert_ok!(Pools::do_reward_payout(50, Delegators::get(50).unwrap(), &bonded_pool)); + assert_ok!(Pools::do_reward_payout(50, &mut del_50, &mut bonded_pool)); // Then // Expect payout of 210: (21,000 / 21,000) * 210 - assert_eq!(Delegators::::get(50).unwrap(), del(50, 620)); + assert_eq!(del_50, del(50, 620)); assert_eq!( RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(0, 21_000 - 50 * 420, 620) @@ -1202,7 +1285,7 @@ mod claim_payout { #[test] fn do_reward_payout_errors_correctly() { ExtBuilder::default().build_and_execute(|| { - let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); // The only place this can return an error is with the balance transfer from the @@ -1217,7 +1300,7 @@ mod claim_payout { // Then // Expect attempt payout of 15/10 * 10 when free balance is actually 10 assert_noop!( - Pools::do_reward_payout(10, delegator, &bonded_pool), + Pools::do_reward_payout(10, &mut delegator, &mut bonded_pool), pallet_balances::Error::::InsufficientBalance ); }); From cbdeafac8eafd14885d33e87f84fd89a3c1d86df Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 11 Mar 2022 15:27:15 +0000 Subject: [PATCH 177/299] Remove some TODOs --- frame/nomination-pools/src/lib.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 834cc0e4553c9..e001dced1d33b 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1269,14 +1269,6 @@ pub mod pallet { // It is not strictly necessary to claim the rewards, but we do it here for UX. Self::do_reward_payout(target.clone(), &mut delegator, &mut bonded_pool)?; - // TODO: make do_reward_payout pass through delegator and bonded_pool - // // Re-fetch the delegator because they where updated by `do_reward_payout`. - // let mut delegator = - // Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; - // // Re-fetch the bonded pool because they where updated by `do_reward_payout`. - // let mut bonded_pool = BondedPool::::get(&delegator.bonded_pool_account) - // .defensive_ok_or_else(|| Error::::PoolNotFound)?; - // Note that we lazily create the unbonding pools here if they don't already exist let sub_pools = SubPoolsStorage::::get(&delegator.bonded_pool_account).unwrap_or_default(); From 5de46f74f061d91e9466f45b3324301516725112 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 11 Mar 2022 17:23:17 +0000 Subject: [PATCH 178/299] Add test for saturating --- frame/nomination-pools/src/lib.rs | 2 -- frame/nomination-pools/src/tests.rs | 52 +++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index e001dced1d33b..fd467b983c3fd 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -636,8 +636,6 @@ impl BondedPool { // We checked for zero above .div(bonded_balance); - println!("{:?}=points", self.points); - println!("{:?}=bonded_balance", bonded_balance); // TODO make sure these checks make sense. Taken from staking design chat with Al // Pool points can inflate relative to balance, but only if the pool is slashed. diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 6d32110985cc0..5c2697c4b41e6 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -748,20 +748,44 @@ mod claim_payout { }); } - // TODO - // #[test] - // fn calculate_delegator_payout_sets_pool_state_to_destroying_if_mul_saturates() { - // ExtBuilder::default().build_and_execute(|| { - // let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); - // let reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); - // let delegator = Delegators::::get(10).unwrap(); - - // let u64_max_u128 = u64::MAX as u128; - // assert!(u64_max_u128.check_mul(u64_max_u128).is_none()); - // Balances::make_free_balance_be(&reward_pool.account, u64::MAX as u128); - // // let reward_pool = - // }); - // } + #[test] + fn do_reward_payout_correctly_sets_pool_state_to_destroying() { + ExtBuilder::default().build_and_execute(|| { + let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut delegator = Delegators::::get(10).unwrap(); + + // --- reward_pool.total_earnings saturates + + // Given + Balances::make_free_balance_be(&reward_pool.account, Balance::MAX); + + // When + assert_ok!(Pools::do_reward_payout(10, &mut delegator, &mut bonded_pool)); + + // Then + assert!(bonded_pool.is_destroying()); + + + // -- current_points saturates (reward_pool.points + new_earnings * bonded_pool.points) + + // Given + let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut delegator = Delegators::::get(10).unwrap(); + // Force new_earnings * bonded_pool.points == 100 + Balances::make_free_balance_be(&reward_pool.account, 10); + assert_eq!(bonded_pool.points, 10); + // Force reward_pool.points == U256::MAX - new_earnings * bonded_pool.points + reward_pool.points = U256::MAX - U256::from(100); + RewardPools::::insert(PRIMARY_ACCOUNT, reward_pool.clone()); + + // When + assert_ok!(Pools::do_reward_payout(10, &mut delegator, &mut bonded_pool)); + + // Then + assert!(bonded_pool.is_destroying()); + }); + } #[test] fn calculate_delegator_payout_errors_if_a_delegator_is_unbonding() { From 4acd856e0f790f55c878f819fc2d936362f98f10 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sat, 12 Mar 2022 09:00:09 +0000 Subject: [PATCH 179/299] feedback --- frame/nomination-pools/src/lib.rs | 207 +++++++++++++++++------------- 1 file changed, 117 insertions(+), 90 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index e001dced1d33b..34ae08f5b7659 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -12,7 +12,7 @@ //! ## Key terms //! //! * bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and -//! [`BondedPoolPoints`]. Bonded pools are identified via the pools bonded account. +//! [`BondedPoolStorage`]. Bonded pools are identified via the pools bonded account. //! * reward pool: Tracks rewards earned by actively staked funds. See [`RewardPool`] and //! [`RewardPools`]. Reward pools are identified via the pools bonded account. //! * unbonding sub pools: Collection of pools at different phases of the unbonding lifecycle. See @@ -283,17 +283,18 @@ //! //! * Watch out for overflow of [`RewardPoints`] and [`BalanceOf`] types. Consider things like the //! chains total issuance, staking reward rate, and burn rate. -// + // Invariants // * A `delegator.bonded_pool_account` must always be a valid entry in `RewardPools`, and -// `BondedPoolPoints`. -// * Every entry in `BondedPoolPoints` must have a corresponding entry in `RewardPools` +// `BondedPoolStorage`. +// * Every entry in `BondedPoolStorage` must have a corresponding entry in `RewardPools` // * If a delegator unbonds, the sub pools should always correctly track slashses such that the // calculated amount when withdrawing unbonded is a lower bound of the pools free balance. // * If the depositor is actively unbonding, the pool is in destroying state. To achieve this, once // a pool is flipped to a destroying state it cannot change its state. // * The sum of each pools delegator counter equals the `Delegators::count()`. // * A pool's `delegator_counter` should always be gt 0. +// * TODO: metadata should only exist if the pool exist. // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -303,7 +304,8 @@ use frame_support::{ pallet_prelude::{MaxEncodedLen, *}, storage::bounded_btree_map::BoundedBTreeMap, traits::{ - Currency, DefensiveOption, DefensiveResult, DefensiveSaturating, ExistenceRequirement, Get, + Currency, Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, + ExistenceRequirement, Get, }, DefaultNoBound, RuntimeDebugNoBound, }; @@ -357,50 +359,7 @@ impl Encode for AccountType { } } -/// Calculate the number of points to issue from a pool as `(current_points / current_balance) * -/// new_funds` except for some zero edge cases; see logic and tests for details. -fn points_to_issue( - current_balance: BalanceOf, - current_points: BalanceOf, - new_funds: BalanceOf, -) -> BalanceOf { - match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), - (true, false) => { - // The pool was totally slashed. - // This is the equivalent of `(current_points / 1) * new_funds`. - new_funds.saturating_mul(current_points) - }, - (false, false) => { - // Equivalent to (current_points / current_balance) * new_funds - current_points - .saturating_mul(new_funds) - // We check for zero above - .div(current_balance) - }, - } -} - -// Calculate the balance of a pool to unbond as `(current_balance / current_points) * -// delegator_points`. Returns zero if any of the inputs are zero. -fn balance_to_unbond( - current_balance: BalanceOf, - current_points: BalanceOf, - delegator_points: BalanceOf, -) -> BalanceOf { - if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { - // There is nothing to unbond - return Zero::zero() - } - - // Equivalent of (current_balance / current_points) * delegator_points - current_balance - .saturating_mul(delegator_points) - // We check for zero above - .div(current_points) -} - +/// A delegator in a pool. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] @@ -423,16 +382,17 @@ pub struct Delegator { /// A pool's possible states. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, RuntimeDebugNoBound, Clone)] pub enum PoolState { - #[codec(index = 0)] + /// The pool is open to be joined, and is working normally. Open, - #[codec(index = 1)] + /// The pool is blocked. No one else can join. Blocked, - #[codec(index = 2)] + /// The pool has been scheduled to destroyed. + /// + /// All delegators can now be permissionlessly unbonded, and the pool can never go back to any + /// other state other than being dissolved. Destroying, } -// TODO: if this is meant to not be used EVER directly, you can enforce that by putting this and -// `BondedPool` into a private `mod {}` and only exporting the stuff you need. // TODO: call Inner /// Pool permissions and state #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq)] @@ -456,21 +416,22 @@ pub struct BondedPoolStorage { pub state_toggler: T::AccountId, } -// module id + reward/bonded + last 20 chars of depositor account id - #[derive(RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] pub struct BondedPool { - /// Points of the pool. Each delegator has some corresponding the points. The portion of points - /// that belong to a delegator represent the portion of the pools bonded funds belong to the - /// delegator. + /// Points of the pool. + /// + /// Each delegator has some corresponding the points. The portion of points that belong to a + /// delegator represent the portion of the pools bonded funds belong to the delegator. points: BalanceOf, /// State of the pool. state: PoolState, /// Count of delegators that belong to this pool. delegator_counter: u32, - /// Account that puts down a deposit to create the pool. This account acts a delegator, but can - /// only unbond if no other delegators belong to the pool. + /// Account that puts down a deposit to create the pool. + /// + /// This account acts a delegator, but can only unbond if no other delegators belong to the + /// pool. depositor: T::AccountId, /// Can perform the same actions as [`Self::nominator`] and [`Self::state_toggler`]. /// Additionally, this account can set the `nominator` and `state_toggler` at any time. @@ -505,7 +466,8 @@ impl BondedPool { // TODO: figure out if we should ditch BondedPoolStorage vs BondedPool and instead just have // BondedPool that doesn't have `account` field. Instead just use deterministic accountId - // generation function. Only downside is this will have some increased computational cost + // generation function. Only downside is this will have some increased computational cost. + /// Get [`Self`] from storage. Returns `None` if no entry for `pool_account` exists. fn get(pool_account: &T::AccountId) -> Option { BondedPools::::try_get(pool_account).ok().map(|storage| Self { @@ -520,7 +482,7 @@ impl BondedPool { }) } - /// Consume and put [`Self`] into storage. + /// Consume self and put into storage. fn put(self) { BondedPools::::insert( self.account, @@ -535,19 +497,28 @@ impl BondedPool { }, ); } - /// Consume and remove [`Self`] from storage. + + /// Consume self and remove from storage. fn remove(self) { BondedPools::::remove(self.account); } + // TODO: put `BondedPool` and `BondedPoolStorage` into a private module, then you have to think + // about which functions need to be public and which not as well. Then make sure public ones are + // documented. + fn create_account(account_type: AccountType, depositor: T::AccountId) -> T::AccountId { // TODO: look into make the prefix transparent by not hashing anything // TODO: look into a using a configurable module id. + // TODO: consult someone experienced about this to figure out faster. let entropy = (b"npls", account_type, depositor).using_encoded(blake2_256); Decode::decode(&mut TrailingZeroInput::new(&entropy)).expect("Infinite length input. qed") } fn reward_account(&self) -> T::AccountId { + // TODO: self.depositor can now change. if you keep this scheme for accounts, you should + // store the reward account in the struct itself. So key it by the bonded account, and the + // reward account is stored in it. OR NEVER allow the depositor to change. Self::create_account(AccountType::Reward, self.depositor.clone()) } @@ -555,14 +526,18 @@ impl BondedPool { fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); - points_to_issue::(bonded_balance, self.points, new_funds) + // TODO: I don't see why these are not functions defined on `BondedPool` rather than + // being a standalone function. + Pallet::::points_to_issue(bonded_balance, self.points, new_funds) } /// Get the amount of balance to unbond from the pool based on a delegator's points of the pool. fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { let bonded_balance = T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); - balance_to_unbond::(bonded_balance, self.points, delegator_points) + // TODO: I don't see why these are not functions defined on `BondedPool` rather than + // being a standalone function. + Pallet::::balance_to_unbond(bonded_balance, self.points, delegator_points) } /// Issue points to [`Self`] for `new_funds`. @@ -575,7 +550,7 @@ impl BondedPool { /// Increment the delegator counter. Ensures that the pool and system delegator limits are /// respected. - fn inc_delegators(&mut self) -> Result<(), DispatchError> { + fn try_inc_delegators(&mut self) -> Result<(), DispatchError> { ensure!( MaxDelegatorsPerPool::::get() .map_or(true, |max_per_pool| self.delegator_counter < max_per_pool), @@ -597,9 +572,10 @@ impl BondedPool { /// The pools balance that is not locked. This assumes the staking system is the only fn non_locked_balance(&self) -> BalanceOf { - T::Currency::free_balance(&self.account).saturating_sub( - T::StakingInterface::locked_balance(&self.account).unwrap_or(Zero::zero()), - ) + // TODO: how can this ever be NON-zero? The pool always bonds it entire stash, and rewards + // go elsewhere. + T::Currency::free_balance(&self.account) + .saturating_sub(T::StakingInterface::locked_balance(&self.account).unwrap_or_default()) } fn can_nominate(&self, who: &T::AccountId) -> bool { @@ -619,7 +595,7 @@ impl BondedPool { } fn is_destroying(&self) -> bool { - self.state == PoolState::Destroying + matches!(self.state, PoolState::Destroying) } /// Whether or not the pool is ok to be in `PoolSate::Open`. If this returns an `Err`, then the @@ -641,9 +617,9 @@ impl BondedPool { // TODO make sure these checks make sense. Taken from staking design chat with Al // Pool points can inflate relative to balance, but only if the pool is slashed. - // // If we cap the ratio of points:balance so one cannot join a pool that has been slashed // 90%, + ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, // TODO instead of this, where we multiply and it could saturate, watch out for being close @@ -660,10 +636,8 @@ impl BondedPool { /// Check that the pool can accept a member with `new_funds`. fn ok_to_join(&self) -> Result<(), DispatchError> { - ensure!(self.state == PoolState::Open, Error::::NotOpen); - self.ok_to_be_open()?; - + ensure!(self.state == PoolState::Open, Error::::NotOpen); Ok(()) } @@ -694,7 +668,7 @@ impl BondedPool { ensure!(target_delegator.points == self.points, Error::::NotOnlyDelegator); ensure!(self.is_destroying(), Error::::NotDestroying); }, - } + }; Ok(()) } @@ -757,7 +731,7 @@ impl BondedPool { amount: BalanceOf, ty: PoolBond, ) -> Result, DispatchError> { - self.inc_delegators()?; + self.try_inc_delegators()?; // Transfer the funds to be bonded from `who` to the pools account so the pool can then // go bond them. @@ -803,6 +777,7 @@ impl BondedPool { } } +/// A reward pool. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] @@ -854,11 +829,11 @@ pub struct UnbondPool { impl UnbondPool { fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { - points_to_issue::(self.balance, self.points, new_funds) + Pallet::::points_to_issue(self.balance, self.points, new_funds) } fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { - balance_to_unbond::(self.balance, self.points, delegator_points) + Pallet::::balance_to_unbond(self.balance, self.points, delegator_points) } /// Issue points and update the balance given `new_balance`. @@ -877,13 +852,15 @@ pub struct SubPools { /// of `Self::with_era` will lazily be merged into into this pool if they are /// older then `current_era - TotalUnbondingPools`. no_era: UnbondPool, - /// Map of era => unbond pools. + /// Map of era in which a pool becomes unbonded in => unbond pools. with_era: SubPoolsWithEra, } impl SubPools { /// Merge the oldest `with_era` unbond pools into the `no_era` unbond pool. + // TODO: Consider not consuming self. fn maybe_merge_pools(mut self, unbond_era: EraIndex) -> Self { + // TODO: remove if unbond_era < TotalUnbondingPools::::get().into() { // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do // anything. Ex: if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool @@ -895,6 +872,7 @@ impl SubPools { // 6..=10. let newest_era_to_remove = unbond_era.saturating_sub(TotalUnbondingPools::::get()); + // TODO: eras to keep, one filter, tweak self.no_era whilst filtering. let eras_to_remove: Vec<_> = self .with_era .keys() @@ -902,7 +880,7 @@ impl SubPools { .filter(|era| *era <= newest_era_to_remove) .collect(); for era in eras_to_remove { - if let Some(p) = self.with_era.remove(&era) { + if let Some(p) = self.with_era.remove(&era).defensive() { self.no_era.points = self.no_era.points.saturating_add(p.points); self.no_era.balance = self.no_era.balance.saturating_add(p.balance); } @@ -916,6 +894,7 @@ impl SubPools { /// The caller must ensure that the `SubPools::with_era` has room for 1 more entry. Calling /// [`SubPools::maybe_merge_pools`] with the current era should the sub pools are in an ok state /// to call this method. + // TODO: dissolve and move to call site. fn unchecked_with_era_get_or_make(&mut self, era: EraIndex) -> &mut UnbondPool { if !self.with_era.contains_key(&era) { self.with_era @@ -958,7 +937,7 @@ pub mod pallet { type WeightInfo: weights::WeightInfo; // TODO: Should this just be part of the StakingInterface trait? We want the currencies to - // be the same anyways + // be the same anyways. /// The nominating balance. type Currency: Currency; @@ -1017,22 +996,20 @@ pub mod pallet { #[pallet::storage] pub type BondedPools = CountedStorageMap<_, Twox64Concat, T::AccountId, BondedPoolStorage>; - // CountedStorageMap<_, Twox64Concat, T::AccountId, BondedPoolStorage>; /// Reward pools. This is where there rewards for each pool accumulate. When a delegators payout - /// is claimed, the balance comes out fo the reward pool. Keyed by the bonded pools - /// _Stash_/_Controller_. + /// is claimed, the balance comes out fo the reward pool. Keyed by the bonded pools account. #[pallet::storage] pub type RewardPools = CountedStorageMap<_, Twox64Concat, T::AccountId, RewardPool>; /// Groups of unbonding pools. Each group of unbonding pools belongs to a bonded pool, - /// hence the name sub-pools. Keyed by the bonded pools _Stash_/_Controller_. + /// hence the name sub-pools. Keyed by the bonded pools account. #[pallet::storage] pub type SubPoolsStorage = CountedStorageMap<_, Twox64Concat, T::AccountId, SubPools>; - /// Metadata for the pool + /// Metadata for the pool. #[pallet::storage] pub type Metadata = CountedStorageMap< _, @@ -1234,11 +1211,12 @@ pub mod pallet { Ok(()) } - /// Unbond _all_ of the `target` delegators funds from the pool. Under certain conditions, - /// this call can be dispatched permissionlessly (i.e. by any account). + /// Unbond _all_ of the `target`'s funds from the pool. Under certain conditions, this call + /// can be dispatched permissionlessly (i.e. by any account). /// /// # Conditions for a permissionless dispatch /// + /// TODO: delegator -> target /// * The pool is blocked and the caller is either the root or state-toggler. This is /// refereed to as a kick. /// * The pool is destroying and the delegator is not the depositor. @@ -1251,7 +1229,7 @@ pub mod pallet { /// * The caller is the depositor, the pool is destroying and no other delegators are in the /// pool. /// - /// Note: If their are too many unlocking chunks to unbond with the pool account, + /// Note: If there are too many unlocking chunks to unbond with the pool account, /// [`Self::withdraw_unbonded_pool`] can be called to try and minimize unlocking chunks. #[pallet::weight(T::WeightInfo::unbond_other())] pub fn unbond_other(origin: OriginFor, target: T::AccountId) -> DispatchResult { @@ -1262,6 +1240,7 @@ pub mod pallet { .defensive_ok_or_else(|| Error::::PoolNotFound)?; bonded_pool.ok_to_unbond_other_with(&caller, &target, &delegator)?; + // alternative: do_reward_payout can report if it changed states. let was_destroying = bonded_pool.is_destroying(); // Claim the the payout prior to unbonding. Once the user is unbonding their points @@ -1282,6 +1261,7 @@ pub mod pallet { bonded_pool.points = bonded_pool.points.saturating_sub(delegator.points); // Unbond in the actual underlying pool + // TODO: can fail after write: better make it transactional. T::StakingInterface::unbond(delegator.bonded_pool_account.clone(), balance_to_unbond)?; // Merge any older pools into the general, era agnostic unbond pool. Note that we do @@ -1300,6 +1280,7 @@ pub mod pallet { pool: delegator.bonded_pool_account.clone(), amount: balance_to_unbond, }); + if bonded_pool.is_destroying() && !was_destroying { // Persist the pool to storage iff the state was updated Self::deposit_event(Event::::State { @@ -1307,6 +1288,7 @@ pub mod pallet { new_state: PoolState::Destroying, }); } + // Now that we know everything has worked write the items to storage. bonded_pool.put(); SubPoolsStorage::insert(&delegator.bonded_pool_account, sub_pools); @@ -1611,6 +1593,50 @@ pub mod pallet { } impl Pallet { + /// Calculate the number of points to issue from a pool as `(current_points / current_balance) * + /// new_funds` except for some zero edge cases; see logic and tests for details. + fn points_to_issue( + current_balance: BalanceOf, + current_points: BalanceOf, + new_funds: BalanceOf, + ) -> BalanceOf { + match (current_balance.is_zero(), current_points.is_zero()) { + (true, true) | (false, true) => + new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), + (true, false) => { + // The pool was totally slashed. + // This is the equivalent of `(current_points / 1) * new_funds`. + new_funds.saturating_mul(current_points) + }, + (false, false) => { + // Equivalent to (current_points / current_balance) * new_funds + current_points + .saturating_mul(new_funds) + // We check for zero above + .div(current_balance) + }, + } + } + + // Calculate the balance of a pool to unbond as `(current_balance / current_points) * + // delegator_points`. Returns zero if any of the inputs are zero. + fn balance_to_unbond( + current_balance: BalanceOf, + current_points: BalanceOf, + delegator_points: BalanceOf, + ) -> BalanceOf { + if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { + // There is nothing to unbond + return Zero::zero() + } + + // Equivalent of (current_balance / current_points) * delegator_points + current_balance + .saturating_mul(delegator_points) + // We check for zero above + .div(current_points) + } + /// Calculate the rewards for `delegator`. fn calculate_delegator_payout( bonded_pool: &mut BondedPool, @@ -1701,6 +1727,7 @@ impl Pallet { delegator_payout, ExistenceRequirement::AllowDeath, )?; + Self::deposit_event(Event::::PaidOut { delegator: delegator_id.clone(), pool: delegator.bonded_pool_account.clone(), From 9721dd1471c547a3aa7bcd9b9fe327ea5167c9bd Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 14 Mar 2022 12:18:28 +0000 Subject: [PATCH 180/299] Fix join test --- frame/nomination-pools/src/lib.rs | 2 +- frame/nomination-pools/src/tests.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 8f7acd80106f6..73f76bbf25a8c 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -634,8 +634,8 @@ impl BondedPool { /// Check that the pool can accept a member with `new_funds`. fn ok_to_join(&self) -> Result<(), DispatchError> { - self.ok_to_be_open()?; ensure!(self.state == PoolState::Open, Error::::NotOpen); + self.ok_to_be_open()?; Ok(()) } diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 5c2697c4b41e6..fdc7925f291c2 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -459,7 +459,7 @@ mod join { // Balance is gt 1/10 of Balance::MAX assert_noop!(Pools::join(Origin::signed(11), 5, 123), Error::::OverflowRisk); - StakingMock::set_bonded_balance(123, 100); + StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 10); // Cannot join a pool that isn't open unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); assert_noop!( From 3b30c3636a8e4831b201e1d95602565351e3d46a Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 15 Mar 2022 08:51:57 +0000 Subject: [PATCH 181/299] use `inner` for nested types in nomination pools (#11030) * Use nested inner type for pool * make tests and benchmarks work * remove feat * all tests work now * fix node-runtime --- bin/node/runtime/src/lib.rs | 2 + .../nomination-pools/benchmarking/Cargo.toml | 2 +- .../nomination-pools/benchmarking/src/lib.rs | 34 +- .../nomination-pools/benchmarking/src/mock.rs | 4 +- frame/nomination-pools/src/lib.rs | 505 +++++----- frame/nomination-pools/src/mock.rs | 32 +- frame/nomination-pools/src/tests.rs | 953 ++++++++---------- 7 files changed, 716 insertions(+), 816 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 230de88a019d3..f148e08fd6526 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -698,6 +698,7 @@ impl pallet_bags_list::Config for Runtime { parameter_types! { pub const PostUnbondPoolsWindow: u32 = 4; + pub const NominationPoolsPalletId: PalletId = PalletId(*b"py/npols"); } use sp_runtime::traits::Convert; @@ -723,6 +724,7 @@ impl pallet_nomination_pools::Config for Runtime { type StakingInterface = pallet_staking::Pallet; type PostUnbondingPoolsWindow = PostUnbondPoolsWindow; type MaxMetadataLen = ConstU32<256>; + type PalletId = NominationPoolsPalletId; } parameter_types! { diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index f6ead8c9af2ad..700e626e79ae0 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -54,4 +54,4 @@ std = [ "pallet-nomination-pools/std", "sp-runtime/std", "sp-staking/std" -] \ No newline at end of file +] diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 03292f1ad09fe..cc3f6b314d8c6 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -11,8 +11,8 @@ use frame_election_provider_support::SortedListProvider; use frame_support::{ensure, traits::Get}; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ - BalanceOf, BondedPoolStorage, BondedPools, Delegators, Metadata, MinCreateBond, MinJoinBond, - Pallet as Pools, PoolState, RewardPools, SubPoolsStorage, + BalanceOf, BondedPoolInner, BondedPools, Delegators, Metadata, MinCreateBond, MinJoinBond, + Pallet as Pools, PoolRoles, PoolState, RewardPools, SubPoolsStorage, }; use sp_runtime::traits::{StaticLookup, Zero}; use sp_staking::{EraIndex, StakingInterface}; @@ -67,7 +67,7 @@ fn create_pool_account( .unwrap(); let pool_account = pallet_nomination_pools::BondedPools::::iter() - .find(|(_, bonded_pool)| bonded_pool.depositor == pool_creator) + .find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator) .map(|(pool_account, _)| pool_account) .expect("pool_creator created a pool above"); @@ -189,7 +189,7 @@ impl ListScenario { // Sanity check the delegator was added correctly let delegator = Delegators::::get(&joiner).unwrap(); assert_eq!(delegator.points, amount); - assert_eq!(delegator.pool, self.origin1); + assert_eq!(delegator.bonded_pool_account, self.origin1); self } @@ -475,14 +475,16 @@ frame_benchmarking::benchmarks! { let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); assert_eq!( new_pool, - BondedPoolStorage { + BondedPoolInner { points: min_create_bond, - depositor: depositor.clone(), - root: depositor.clone(), - nominator: depositor.clone(), - state_toggler: depositor.clone(), state: PoolState::Open, - delegator_counter: 1 + delegator_counter: 1, + roles: PoolRoles { + depositor: depositor.clone(), + root: depositor.clone(), + nominator: depositor.clone(), + state_toggler: depositor.clone(), + }, } ); assert_eq!( @@ -516,14 +518,16 @@ frame_benchmarking::benchmarks! { let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); assert_eq!( new_pool, - BondedPoolStorage { + BondedPoolInner { points: min_create_bond, - depositor: depositor.clone(), - root: depositor.clone(), - nominator: depositor.clone(), - state_toggler: depositor.clone(), state: PoolState::Open, delegator_counter: 1, + roles: PoolRoles { + depositor: depositor.clone(), + root: depositor.clone(), + nominator: depositor.clone(), + state_toggler: depositor.clone(), + } } ); assert_eq!( diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 0e89ed09fe6e1..895b9b672c96d 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -16,7 +16,7 @@ // limitations under the License. use frame_election_provider_support::VoteWeight; -use frame_support::{pallet_prelude::*, parameter_types, traits::ConstU64}; +use frame_support::{pallet_prelude::*, parameter_types, traits::ConstU64, PalletId}; use sp_runtime::traits::{Convert, IdentityLookup}; type AccountId = u64; @@ -141,6 +141,7 @@ impl Convert for U256ToBalance { parameter_types! { pub static PostUnbondingPoolsWindow: u32 = 10; + pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); } impl pallet_nomination_pools::Config for Runtime { @@ -152,6 +153,7 @@ impl pallet_nomination_pools::Config for Runtime { type StakingInterface = Staking; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; type MaxMetadataLen = ConstU32<256>; + type PalletId = PoolsPalletId; } impl crate::Config for Runtime {} diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 73f76bbf25a8c..2ac769b50a506 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -12,7 +12,7 @@ //! ## Key terms //! //! * bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and -//! [`BondedPoolStorage`]. Bonded pools are identified via the pools bonded account. +//! [`BondedPoolInner`]. Bonded pools are identified via the pools bonded account. //! * reward pool: Tracks rewards earned by actively staked funds. See [`RewardPool`] and //! [`RewardPools`]. Reward pools are identified via the pools bonded account. //! * unbonding sub pools: Collection of pools at different phases of the unbonding lifecycle. See @@ -286,8 +286,8 @@ // Invariants // * A `delegator.bonded_pool_account` must always be a valid entry in `RewardPools`, and -// `BondedPoolStorage`. -// * Every entry in `BondedPoolStorage` must have a corresponding entry in `RewardPools` +// `BondedPoolInner`. +// * Every entry in `BondedPoolInner` must have a corresponding entry in `RewardPools` // * If a delegator unbonds, the sub pools should always correctly track slashses such that the // calculated amount when withdrawing unbonded is a lower bound of the pools free balance. // * If the depositor is actively unbonding, the pool is in destroying state. To achieve this, once @@ -311,8 +311,7 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_core::U256; -use sp_io::hashing::blake2_256; -use sp_runtime::traits::{Bounded, Convert, Saturating, StaticLookup, TrailingZeroInput, Zero}; +use sp_runtime::traits::{AccountIdConversion, Bounded, Convert, Saturating, StaticLookup, Zero}; use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, ops::Div, vec::Vec}; @@ -331,8 +330,9 @@ pub use weights::WeightInfo; pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; pub type SubPoolsWithEra = BoundedBTreeMap, TotalUnbondingPools>; -// NOTE: this assumes the balance type u128 or smaller. -type RewardPoints = U256; +// NOTE: this assumes the balance type u128 or smaller. TODO: integrity-check +pub type RewardPoints = U256; +pub type PoolId = u32; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; @@ -342,31 +342,14 @@ enum PoolBond { Join, } -/// Identifier for encoding different pool account types. -enum AccountType { - /// The bonded account of the pool. This is functionally both the stash and controller account. - Bonded, - /// The reward account of the pool. - Reward, -} - -impl Encode for AccountType { - fn encode(&self) -> Vec { - match self { - Self::Bonded => b"bond".to_vec(), - Self::Reward => b"rewd".to_vec(), - } - } -} - /// A delegator in a pool. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct Delegator { - /// The bonded account of the pool. - pub bonded_pool_account: T::AccountId, + /// The identifier of the pool to which `who` belongs. + pub pool_id: PoolId, /// The quantity of points this delegator has in the bonded pool or in a sub pool if /// `Self::unbonding_era` is some. pub points: BalanceOf, @@ -380,7 +363,7 @@ pub struct Delegator { } /// A pool's possible states. -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, RuntimeDebugNoBound, Clone)] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, RuntimeDebugNoBound, Clone, Copy)] pub enum PoolState { /// The pool is open to be joined, and is working normally. Open, @@ -393,13 +376,19 @@ pub enum PoolState { Destroying, } -// TODO: call Inner +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone)] +pub struct PoolRoles { + pub depositor: AccountId, + pub root: AccountId, + pub nominator: AccountId, + pub state_toggler: AccountId, +} + /// Pool permissions and state -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq)] -#[cfg_attr(feature = "std", derive(Clone))] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone)] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] -pub struct BondedPoolStorage { +pub struct BondedPoolInner { /// See [`BondedPool::points`]. pub points: BalanceOf, /// See [`BondedPool::state_toggler`]. @@ -407,136 +396,86 @@ pub struct BondedPoolStorage { /// See [`BondedPool::delegator_counter`] pub delegator_counter: u32, /// See [`BondedPool::depositor`]. - pub depositor: T::AccountId, - /// See [`BondedPool::admin`]. - pub root: T::AccountId, - /// See [`BondedPool::nominator`]. - pub nominator: T::AccountId, - /// See [`BondedPool::state_toggler`]. - pub state_toggler: T::AccountId, + pub roles: PoolRoles, } +/// A wrapper for bonded pools, with utility functions. +/// +/// The main purpose of this is to wrap a [`BondedPoolInner`], with the account + id of the pool, +/// for easier access. #[derive(RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] pub struct BondedPool { - /// Points of the pool. - /// - /// Each delegator has some corresponding the points. The portion of points that belong to a - /// delegator represent the portion of the pools bonded funds belong to the delegator. - points: BalanceOf, - /// State of the pool. - state: PoolState, - /// Count of delegators that belong to this pool. - delegator_counter: u32, - /// Account that puts down a deposit to create the pool. - /// - /// This account acts a delegator, but can only unbond if no other delegators belong to the - /// pool. - depositor: T::AccountId, - /// Can perform the same actions as [`Self::nominator`] and [`Self::state_toggler`]. - /// Additionally, this account can set the `nominator` and `state_toggler` at any time. - root: T::AccountId, - /// Can set the pool's nominations at any time. - nominator: T::AccountId, - /// Can toggle the pools state, including setting the pool as blocked or putting the pool into - /// destruction mode. The state toggle can also "kick" delegators by unbonding them. - state_toggler: T::AccountId, - /// AccountId of the pool. - account: T::AccountId, + /// The identifier of the pool. + id: PoolId, + /// The inner fields. + inner: BondedPoolInner, +} + +impl sp_std::ops::Deref for BondedPool { + type Target = BondedPoolInner; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +// TODO: ask a rust guy if this is a bad thing to do. +impl sp_std::ops::DerefMut for BondedPool { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } impl BondedPool { - fn new( - depositor: T::AccountId, - root: T::AccountId, - nominator: T::AccountId, - state_toggler: T::AccountId, - ) -> Self { + /// Create a new bonded pool with the given roles and identifier. + fn new(id: PoolId, roles: PoolRoles) -> Self { Self { - account: Self::create_account(AccountType::Bonded, depositor.clone()), - depositor, - root, - nominator, - state_toggler, - state: PoolState::Open, - points: Zero::zero(), - delegator_counter: Zero::zero(), + id, + inner: BondedPoolInner { + roles, + state: PoolState::Open, + points: Zero::zero(), + delegator_counter: Zero::zero(), + }, } } - // TODO: figure out if we should ditch BondedPoolStorage vs BondedPool and instead just have - // BondedPool that doesn't have `account` field. Instead just use deterministic accountId - // generation function. Only downside is this will have some increased computational cost. - /// Get [`Self`] from storage. Returns `None` if no entry for `pool_account` exists. - fn get(pool_account: &T::AccountId) -> Option { - BondedPools::::try_get(pool_account).ok().map(|storage| Self { - points: storage.points, - delegator_counter: storage.delegator_counter, - state_toggler: storage.state_toggler, - depositor: storage.depositor, - root: storage.root, - nominator: storage.nominator, - state: storage.state, - account: pool_account.clone(), - }) + fn get(id: PoolId) -> Option { + BondedPools::::try_get(id).ok().map(|inner| Self { id, inner }) } - /// Consume self and put into storage. - fn put(self) { - BondedPools::::insert( - self.account, - BondedPoolStorage { - points: self.points, - delegator_counter: self.delegator_counter, - depositor: self.depositor, - root: self.root, - nominator: self.nominator, - state_toggler: self.state_toggler, - state: self.state, - }, - ); + /// Get the bonded account id of this pool. + fn bonded_account(&self) -> T::AccountId { + Pallet::::create_bonded_account(self.id) } - /// Consume self and remove from storage. - fn remove(self) { - BondedPools::::remove(self.account); + /// Get the reward account id of this pool. + fn reward_account(&self) -> T::AccountId { + Pallet::::create_reward_account(self.id) } - // TODO: put `BondedPool` and `BondedPoolStorage` into a private module, then you have to think - // about which functions need to be public and which not as well. Then make sure public ones are - // documented. - - fn create_account(account_type: AccountType, depositor: T::AccountId) -> T::AccountId { - // TODO: look into make the prefix transparent by not hashing anything - // TODO: look into a using a configurable module id. - // TODO: consult someone experienced about this to figure out faster. - let entropy = (b"npls", account_type, depositor).using_encoded(blake2_256); - Decode::decode(&mut TrailingZeroInput::new(&entropy)).expect("Infinite length input. qed") + /// Consume self and put into storage. + fn put(self) { + BondedPools::::insert(self.id, BondedPoolInner { ..self.inner }); } - fn reward_account(&self) -> T::AccountId { - // TODO: self.depositor can now change. if you keep this scheme for accounts, you should - // store the reward account in the struct itself. So key it by the bonded account, and the - // reward account is stored in it. OR NEVER allow the depositor to change. - Self::create_account(AccountType::Reward, self.depositor.clone()) + /// Consume self and remove from storage. + fn remove(self) { + BondedPools::::remove(self.id); } /// Get the amount of points to issue for some new funds that will be bonded in the pool. fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { let bonded_balance = - T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); - // TODO: I don't see why these are not functions defined on `BondedPool` rather than - // being a standalone function. + T::StakingInterface::bonded_balance(&self.bonded_account()).unwrap_or(Zero::zero()); Pallet::::points_to_issue(bonded_balance, self.points, new_funds) } /// Get the amount of balance to unbond from the pool based on a delegator's points of the pool. fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { let bonded_balance = - T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); - // TODO: I don't see why these are not functions defined on `BondedPool` rather than - // being a standalone function. + T::StakingInterface::bonded_balance(&self.bonded_account()).unwrap_or(Zero::zero()); Pallet::::balance_to_unbond(bonded_balance, self.points, delegator_points) } @@ -572,26 +511,26 @@ impl BondedPool { /// The pools balance that is not locked. This assumes the staking system is the only fn non_locked_balance(&self) -> BalanceOf { - // TODO: how can this ever be NON-zero? The pool always bonds it entire stash, and rewards - // go elsewhere. - T::Currency::free_balance(&self.account) - .saturating_sub(T::StakingInterface::locked_balance(&self.account).unwrap_or_default()) + let account = self.bonded_account(); + T::Currency::free_balance(&account) + .saturating_sub(T::StakingInterface::locked_balance(&account).unwrap_or_default()) } fn can_nominate(&self, who: &T::AccountId) -> bool { - *who == self.root || *who == self.nominator + *who == self.roles.root || *who == self.roles.nominator } fn can_kick(&self, who: &T::AccountId) -> bool { - *who == self.root || *who == self.state_toggler && self.state == PoolState::Blocked + *who == self.roles.root || + *who == self.roles.state_toggler && self.state == PoolState::Blocked } fn can_toggle_state(&self, who: &T::AccountId) -> bool { - *who == self.root || *who == self.state_toggler && !self.is_destroying() + *who == self.roles.root || *who == self.roles.state_toggler && !self.is_destroying() } fn can_set_metadata(&self, who: &T::AccountId) -> bool { - *who == self.root || *who == self.state_toggler + *who == self.roles.root || *who == self.roles.state_toggler } fn is_destroying(&self) -> bool { @@ -604,7 +543,7 @@ impl BondedPool { ensure!(!self.is_destroying(), Error::::CanNotChangeState); let bonded_balance = - T::StakingInterface::bonded_balance(&self.account).unwrap_or(Zero::zero()); + T::StakingInterface::bonded_balance(&self.bonded_account()).unwrap_or(Zero::zero()); ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); let points_to_balance_ratio_floor = self @@ -646,7 +585,7 @@ impl BondedPool { target_delegator: &Delegator, ) -> Result<(), DispatchError> { let is_permissioned = caller == target_account; - let is_depositor = *target_account == self.depositor; + let is_depositor = *target_account == self.roles.depositor; match (is_permissioned, is_depositor) { // If the pool is blocked, then an admin with kicking permissions can remove a // delegator. If the pool is being destroyed, anyone can remove a delegator @@ -678,7 +617,7 @@ impl BondedPool { target_delegator: &Delegator, sub_pools: &SubPools, ) -> Result { - if *target_account == self.depositor { + if *target_account == self.roles.depositor { // This is a depositor if !sub_pools.no_era.points.is_zero() { // Unbonded pool has some points, so if they are the last delegator they must be @@ -735,7 +674,7 @@ impl BondedPool { // go bond them. T::Currency::transfer( &who, - &self.account, + &self.bonded_account(), amount, match ty { PoolBond::Create => ExistenceRequirement::AllowDeath, @@ -749,16 +688,16 @@ impl BondedPool { match ty { // TODO: Consider making StakingInterface use reference. PoolBond::Create => T::StakingInterface::bond( - self.account.clone(), + self.bonded_account(), // We make the stash and controller the same for simplicity - self.account.clone(), + self.bonded_account(), amount, self.reward_account(), )?, // The pool should always be created in such a way its in a state to bond extra, but if // the active balance is slashed below the minimum bonded or the account cannot be // found, we exit early. - PoolBond::Join => T::StakingInterface::bond_extra(self.account.clone(), amount)?, + PoolBond::Join => T::StakingInterface::bond_extra(self.bonded_account(), amount)?, } Ok(points_issued) @@ -781,8 +720,6 @@ impl BondedPool { #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct RewardPool { - /// The reward destination for the pool. - pub account: T::AccountId, /// The balance of this reward pool after the last claimed payout. pub balance: BalanceOf, /// The total earnings _ever_ of this reward pool after the last claimed payout. I.E. the sum @@ -798,8 +735,9 @@ pub struct RewardPool { impl RewardPool { /// Mutate the reward pool by updating the total earnings and current free balance. - fn update_total_earnings_and_balance(&mut self) { - let current_balance = T::Currency::free_balance(&self.account); + fn update_total_earnings_and_balance(&mut self, id: PoolId) { + // TODO: not happy with this. + let current_balance = T::Currency::free_balance(&Pallet::::create_reward_account(id)); // The earnings since the last time it was updated let new_earnings = current_balance.saturating_sub(self.balance); // The lifetime earnings of the of the reward pool @@ -808,9 +746,9 @@ impl RewardPool { } /// Get a reward pool and update its total earnings and balance - fn get_and_update(bonded_pool_account: &T::AccountId) -> Option { - RewardPools::::get(bonded_pool_account).map(|mut r| { - r.update_total_earnings_and_balance(); + fn get_and_update(id: PoolId) -> Option { + RewardPools::::get(id).map(|mut r| { + r.update_total_earnings_and_balance(id); r }) } @@ -939,6 +877,10 @@ pub mod pallet { /// The nominating balance. type Currency: Currency; + /// The nomination pool's pallet id. + #[pallet::constant] + type PalletId: Get; + /// Infallible method for converting `Currency::Balance` to `U256`. type BalanceToU256: Convert, U256>; @@ -993,29 +935,29 @@ pub mod pallet { /// To get or insert a pool see [`BondedPool::get`] and [`BondedPool::put`] #[pallet::storage] pub type BondedPools = - CountedStorageMap<_, Twox64Concat, T::AccountId, BondedPoolStorage>; + CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner>; /// Reward pools. This is where there rewards for each pool accumulate. When a delegators payout /// is claimed, the balance comes out fo the reward pool. Keyed by the bonded pools account. #[pallet::storage] - pub type RewardPools = - CountedStorageMap<_, Twox64Concat, T::AccountId, RewardPool>; + pub type RewardPools = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool>; /// Groups of unbonding pools. Each group of unbonding pools belongs to a bonded pool, /// hence the name sub-pools. Keyed by the bonded pools account. #[pallet::storage] - pub type SubPoolsStorage = - CountedStorageMap<_, Twox64Concat, T::AccountId, SubPools>; + pub type SubPoolsStorage = CountedStorageMap<_, Twox64Concat, PoolId, SubPools>; /// Metadata for the pool. #[pallet::storage] - pub type Metadata = CountedStorageMap< - _, - Twox64Concat, - T::AccountId, - BoundedVec, - ValueQuery, - >; + pub type Metadata = + CountedStorageMap<_, Twox64Concat, PoolId, BoundedVec, ValueQuery>; + + #[pallet::storage] + pub type LastPoolId = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + pub type ReversePoolIdLookup = + CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>; #[pallet::genesis_config] pub struct GenesisConfig { @@ -1059,13 +1001,13 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - Created { pool: T::AccountId, depositor: T::AccountId }, - Joined { delegator: T::AccountId, pool: T::AccountId, bonded: BalanceOf }, - PaidOut { delegator: T::AccountId, pool: T::AccountId, payout: BalanceOf }, - Unbonded { delegator: T::AccountId, pool: T::AccountId, amount: BalanceOf }, - Withdrawn { delegator: T::AccountId, pool: T::AccountId, amount: BalanceOf }, - Destroyed { pool: T::AccountId }, - State { pool: T::AccountId, new_state: PoolState }, + Created { depositor: T::AccountId, pool_id: PoolId }, + Joined { delegator: T::AccountId, pool_id: PoolId, bonded: BalanceOf }, + PaidOut { delegator: T::AccountId, pool_id: PoolId, payout: BalanceOf }, + Unbonded { delegator: T::AccountId, pool_id: PoolId, amount: BalanceOf }, + Withdrawn { delegator: T::AccountId, pool_id: PoolId, amount: BalanceOf }, + Destroyed { pool_id: PoolId }, + State { pool_id: PoolId, new_state: PoolState }, } #[pallet::error] @@ -1094,9 +1036,6 @@ pub mod pallet { MinimumBondNotMet, /// The transaction could not be executed due to overflow risk for the pool. OverflowRisk, - // Likely only an error ever encountered in poorly built tests. - /// A pool with the generated account id already exists. - IdInUse, /// A pool must be in [`PoolState::Destroying`] in order for the depositor to unbond or for /// other delegators to be permissionlessly unbonded. NotDestroying, @@ -1134,24 +1073,19 @@ pub mod pallet { /// * Only a pool with [`PoolState::Open`] can be joined #[pallet::weight(T::WeightInfo::join())] #[frame_support::transactional] - pub fn join( - origin: OriginFor, - amount: BalanceOf, - pool_account: T::AccountId, - ) -> DispatchResult { + pub fn join(origin: OriginFor, amount: BalanceOf, pool_id: PoolId) -> DispatchResult { let who = ensure_signed(origin)?; ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); // If a delegator already exists that means they already belong to a pool ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); - let mut bonded_pool = - BondedPool::::get(&pool_account).ok_or(Error::::PoolNotFound)?; + let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; bonded_pool.ok_to_join()?; // don't actually care about writing the reward pool, we just need its total earnings at // this point in time. - let reward_pool = RewardPool::::get_and_update(&pool_account) + let reward_pool = RewardPool::::get_and_update(pool_id) .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; let points_issued = bonded_pool.try_bond_delegator(&who, amount, PoolBond::Join)?; @@ -1159,7 +1093,7 @@ pub mod pallet { Delegators::insert( who.clone(), Delegator:: { - bonded_pool_account: pool_account.clone(), + pool_id, points: points_issued, // At best the reward pool has the rewards up through the previous era. If the // delegator joins prior to the snapshot they will benefit from the rewards of @@ -1172,11 +1106,7 @@ pub mod pallet { }, ); bonded_pool.put(); - Self::deposit_event(Event::::Joined { - delegator: who, - pool: pool_account, - bonded: amount, - }); + Self::deposit_event(Event::::Joined { delegator: who, pool_id, bonded: amount }); Ok(()) } @@ -1191,7 +1121,7 @@ pub mod pallet { pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let mut delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; - let mut bonded_pool = BondedPool::::get(&delegator.bonded_pool_account) + let mut bonded_pool = BondedPool::::get(delegator.pool_id) .defensive_ok_or_else(|| Error::::PoolNotFound)?; let was_destroying = bonded_pool.is_destroying(); @@ -1199,7 +1129,7 @@ pub mod pallet { if bonded_pool.is_destroying() && !was_destroying { Self::deposit_event(Event::::State { - pool: bonded_pool.account.clone(), + pool_id: delegator.pool_id, new_state: PoolState::Destroying, }); } @@ -1214,7 +1144,6 @@ pub mod pallet { /// /// # Conditions for a permissionless dispatch /// - /// TODO: delegator -> target /// * The pool is blocked and the caller is either the root or state-toggler. This is /// refereed to as a kick. /// * The pool is destroying and the delegator is not the depositor. @@ -1230,13 +1159,16 @@ pub mod pallet { /// Note: If there are too many unlocking chunks to unbond with the pool account, /// [`Self::withdraw_unbonded_pool`] can be called to try and minimize unlocking chunks. #[pallet::weight(T::WeightInfo::unbond_other())] - pub fn unbond_other(origin: OriginFor, target: T::AccountId) -> DispatchResult { + pub fn unbond_other( + origin: OriginFor, + delegator_account: T::AccountId, + ) -> DispatchResult { let caller = ensure_signed(origin)?; let mut delegator = - Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; - let mut bonded_pool = BondedPool::::get(&delegator.bonded_pool_account) + Delegators::::get(&delegator_account).ok_or(Error::::DelegatorNotFound)?; + let mut bonded_pool = BondedPool::::get(delegator.pool_id) .defensive_ok_or_else(|| Error::::PoolNotFound)?; - bonded_pool.ok_to_unbond_other_with(&caller, &target, &delegator)?; + bonded_pool.ok_to_unbond_other_with(&caller, &delegator_account, &delegator)?; // alternative: do_reward_payout can report if it changed states. let was_destroying = bonded_pool.is_destroying(); @@ -1244,11 +1176,10 @@ pub mod pallet { // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the bonded pool and thus they can no longer claim their payouts. // It is not strictly necessary to claim the rewards, but we do it here for UX. - Self::do_reward_payout(target.clone(), &mut delegator, &mut bonded_pool)?; + Self::do_reward_payout(delegator_account.clone(), &mut delegator, &mut bonded_pool)?; // Note that we lazily create the unbonding pools here if they don't already exist - let sub_pools = - SubPoolsStorage::::get(&delegator.bonded_pool_account).unwrap_or_default(); + let sub_pools = SubPoolsStorage::::get(delegator.pool_id).unwrap_or_default(); let current_era = T::StakingInterface::current_era(); let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); @@ -1260,7 +1191,7 @@ pub mod pallet { // Unbond in the actual underlying pool // TODO: can fail after write: better make it transactional. - T::StakingInterface::unbond(delegator.bonded_pool_account.clone(), balance_to_unbond)?; + T::StakingInterface::unbond(bonded_pool.bonded_account(), balance_to_unbond)?; // Merge any older pools into the general, era agnostic unbond pool. Note that we do // this before inserting to ensure we don't go over the max unbonding pools. @@ -1274,23 +1205,23 @@ pub mod pallet { delegator.unbonding_era = Some(unbond_era); Self::deposit_event(Event::::Unbonded { - delegator: target.clone(), - pool: delegator.bonded_pool_account.clone(), + delegator: delegator_account.clone(), + pool_id: delegator.pool_id, amount: balance_to_unbond, }); if bonded_pool.is_destroying() && !was_destroying { // Persist the pool to storage iff the state was updated Self::deposit_event(Event::::State { - pool: bonded_pool.account.clone(), + pool_id: delegator.pool_id, new_state: PoolState::Destroying, }); } // Now that we know everything has worked write the items to storage. bonded_pool.put(); - SubPoolsStorage::insert(&delegator.bonded_pool_account, sub_pools); - Delegators::insert(target, delegator); + SubPoolsStorage::insert(&delegator.pool_id, sub_pools); + Delegators::insert(delegator_account, delegator); Ok(()) } @@ -1302,15 +1233,15 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))] pub fn pool_withdraw_unbonded( origin: OriginFor, - pool_account: T::AccountId, + pool_id: PoolId, num_slashing_spans: u32, ) -> DispatchResult { let _ = ensure_signed(origin)?; - let pool = BondedPool::::get(&pool_account).ok_or(Error::::PoolNotFound)?; + let pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; // For now we only allow a pool to withdraw unbonded if its not destroying. If the pool // is destroying then `withdraw_unbonded_other` can be used. ensure!(pool.state != PoolState::Destroying, Error::::NotDestroying); - T::StakingInterface::withdraw_unbonded(pool_account, num_slashing_spans)?; + T::StakingInterface::withdraw_unbonded(pool.bonded_account(), num_slashing_spans)?; Ok(()) } @@ -1335,26 +1266,31 @@ pub mod pallet { )] pub fn withdraw_unbonded_other( origin: OriginFor, - target: T::AccountId, + delegator_account: T::AccountId, num_slashing_spans: u32, ) -> DispatchResultWithPostInfo { let caller = ensure_signed(origin)?; - let delegator = Delegators::::get(&target).ok_or(Error::::DelegatorNotFound)?; + let delegator = + Delegators::::get(&delegator_account).ok_or(Error::::DelegatorNotFound)?; let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); ensure!(current_era >= unbonding_era, Error::::NotUnbondedYet); - let mut sub_pools = SubPoolsStorage::::get(&delegator.bonded_pool_account) + let mut sub_pools = SubPoolsStorage::::get(delegator.pool_id) .defensive_ok_or_else(|| Error::::SubPoolsNotFound)?; - let bonded_pool = BondedPool::::get(&delegator.bonded_pool_account) + let bonded_pool = BondedPool::::get(delegator.pool_id) .defensive_ok_or_else(|| Error::::PoolNotFound)?; - let should_remove_pool = bonded_pool - .ok_to_withdraw_unbonded_other_with(&caller, &target, &delegator, &sub_pools)?; + let should_remove_pool = bonded_pool.ok_to_withdraw_unbonded_other_with( + &caller, + &delegator_account, + &delegator, + &sub_pools, + )?; // Before calculate the `balance_to_unbond`, with call withdraw unbonded to ensure the // `non_locked_balance` is correct. T::StakingInterface::withdraw_unbonded( - delegator.bonded_pool_account.clone(), + bonded_pool.bonded_account(), num_slashing_spans, )?; @@ -1390,38 +1326,51 @@ pub mod pallet { .min(bonded_pool.non_locked_balance()); T::Currency::transfer( - &delegator.bonded_pool_account, - &target, + &bonded_pool.bonded_account(), + &delegator_account, balance_to_unbond, ExistenceRequirement::AllowDeath, ) .defensive_map_err(|e| e)?; Self::deposit_event(Event::::Withdrawn { - delegator: target.clone(), - pool: delegator.bonded_pool_account.clone(), + delegator: delegator_account.clone(), + pool_id: delegator.pool_id, amount: balance_to_unbond, }); let post_info_weight = if should_remove_pool { - let reward_pool = RewardPools::::take(&delegator.bonded_pool_account) - .defensive_ok_or_else(|| Error::::PoolNotFound)?; - Self::deposit_event(Event::::Destroyed { - pool: delegator.bonded_pool_account.clone(), - }); - SubPoolsStorage::::remove(&delegator.bonded_pool_account); + ReversePoolIdLookup::::remove(bonded_pool.bonded_account()); + RewardPools::::remove(delegator.pool_id); + Self::deposit_event(Event::::Destroyed { pool_id: delegator.pool_id }); + SubPoolsStorage::::remove(delegator.pool_id); // Kill accounts from storage by making their balance go below ED. We assume that // the accounts have no references that would prevent destruction once we get to // this point. - T::Currency::make_free_balance_be(&reward_pool.account, Zero::zero()); - T::Currency::make_free_balance_be(&bonded_pool.account, Zero::zero()); + // TODO: in correct scenario, these two accounts should be zero when we reach there + // anyway. + debug_assert_eq!( + T::Currency::free_balance(&bonded_pool.reward_account()), + Zero::zero() + ); + debug_assert_eq!( + T::Currency::free_balance(&bonded_pool.bonded_account()), + Zero::zero() + ); + debug_assert_eq!( + T::StakingInterface::locked_balance(&bonded_pool.bonded_account()) + .unwrap_or_default(), + Zero::zero() + ); + T::Currency::make_free_balance_be(&bonded_pool.reward_account(), Zero::zero()); + T::Currency::make_free_balance_be(&bonded_pool.bonded_account(), Zero::zero()); bonded_pool.remove(); None } else { bonded_pool.dec_delegators().put(); - SubPoolsStorage::::insert(&delegator.bonded_pool_account, sub_pools); + SubPoolsStorage::::insert(&delegator.pool_id, sub_pools); Some(T::WeightInfo::withdraw_unbonded_other_update(num_slashing_spans)) }; - Delegators::::remove(&target); + Delegators::::remove(&delegator_account); Ok(post_info_weight.into()) } @@ -1463,35 +1412,36 @@ pub mod pallet { ); ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); - let mut bonded_pool = BondedPool::::new(who.clone(), root, nominator, state_toggler); - // This shouldn't be possible since we are ensured the delegator is not a depositor and - // the the account ID is generated based on the accountId - ensure!(!BondedPools::::contains_key(&bonded_pool.account), Error::::IdInUse); + let pool_id = LastPoolId::::mutate(|id| { + *id += 1; + *id + }); + let mut bonded_pool = BondedPool::::new( + pool_id, + PoolRoles { root, nominator, state_toggler, depositor: who.clone() }, + ); - let points_issued = bonded_pool.try_bond_delegator(&who, amount, PoolBond::Create)?; + let points = bonded_pool.try_bond_delegator(&who, amount, PoolBond::Create)?; - Self::deposit_event(Event::::Created { - depositor: who.clone(), - pool: bonded_pool.account.clone(), - }); Delegators::::insert( - who, + who.clone(), Delegator:: { - bonded_pool_account: bonded_pool.account.clone(), - points: points_issued, + pool_id, + points, reward_pool_total_earnings: Zero::zero(), unbonding_era: None, }, ); RewardPools::::insert( - bonded_pool.account.clone(), + pool_id, RewardPool:: { balance: Zero::zero(), points: U256::zero(), total_earnings: Zero::zero(), - account: bonded_pool.reward_account(), }, ); + ReversePoolIdLookup::::insert(bonded_pool.bonded_account(), pool_id); + Self::deposit_event(Event::::Created { depositor: who, pool_id }); bonded_pool.put(); Ok(()) @@ -1500,28 +1450,25 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::nominate())] pub fn nominate( origin: OriginFor, - pool_account: T::AccountId, + pool_id: PoolId, validators: Vec<::Source>, ) -> DispatchResult { let who = ensure_signed(origin)?; - let bonded_pool = - BondedPool::::get(&pool_account).ok_or(Error::::PoolNotFound)?; + let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); - T::StakingInterface::nominate(pool_account.clone(), validators)?; + T::StakingInterface::nominate(bonded_pool.bonded_account(), validators)?; Ok(()) } #[pallet::weight(T::WeightInfo::set_state_other())] pub fn set_state_other( origin: OriginFor, - pool_account: T::AccountId, + pool_id: PoolId, state: PoolState, ) -> DispatchResult { let who = ensure_signed(origin)?; - let mut bonded_pool = - BondedPool::::get(&pool_account).ok_or(Error::::PoolNotFound)?; + let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; ensure!(bonded_pool.state != PoolState::Destroying, Error::::CanNotChangeState); - // TODO: [now] we could check if bonded_pool.ok_to_be_open().is_err(), and if thats // true always set the state to destroying, regardless of the stat the caller passes. // The downside is that this seems like a misleading API @@ -1536,7 +1483,7 @@ pub mod pallet { } Self::deposit_event(Event::::State { - pool: bonded_pool.account.clone(), + pool_id, new_state: bonded_pool.state.clone(), }); @@ -1548,20 +1495,20 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::set_metadata())] pub fn set_metadata( origin: OriginFor, - pool_account: T::AccountId, + pool_id: PoolId, metadata: Vec, ) -> DispatchResult { let who = ensure_signed(origin)?; let metadata: BoundedVec<_, _> = metadata.try_into().map_err(|_| Error::::MetadataExceedsMaxLen)?; ensure!( - BondedPool::::get(&pool_account) + BondedPool::::get(pool_id) .ok_or(Error::::PoolNotFound)? .can_set_metadata(&who), Error::::DoesNotHavePermission ); - Metadata::::mutate(&pool_account, |pool_meta| *pool_meta = metadata); + Metadata::::mutate(pool_id, |pool_meta| *pool_meta = metadata); Ok(()) } @@ -1591,6 +1538,18 @@ pub mod pallet { } impl Pallet { + /// Create the main, bonded account of a pool with the given id. + fn create_bonded_account(id: PoolId) -> T::AccountId { + T::PalletId::get().into_sub_account((1u8, id)) + } + + /// Create the reward account of a pool with the given id. + fn create_reward_account(id: PoolId) -> T::AccountId { + // TODO: integrity check for what is the reasonable max number of pools based on this. + // 4 + 8 + 4 + 1 + T::PalletId::get().into_sub_account((2u8, id)) + } + /// Calculate the number of points to issue from a pool as `(current_points / current_balance) * /// new_funds` except for some zero edge cases; see logic and tests for details. fn points_to_issue( @@ -1641,6 +1600,7 @@ impl Pallet { reward_pool: &mut RewardPool, delegator: &mut Delegator, ) -> Result, DispatchError> { + debug_assert_eq!(delegator.pool_id, bonded_pool.id); let u256 = |x| T::BalanceToU256::convert(x); let balance = |x| T::U256ToBalance::convert(x); // If the delegator is unbonding they cannot claim rewards. Note that when the delegator @@ -1648,7 +1608,7 @@ impl Pallet { ensure!(delegator.unbonding_era.is_none(), Error::::AlreadyUnbonding); let last_total_earnings = reward_pool.total_earnings; - reward_pool.update_total_earnings_and_balance(); + reward_pool.update_total_earnings_and_balance(bonded_pool.id); // Notice there is an edge case where total_earnings have not increased and this is zero let new_earnings = u256(reward_pool.total_earnings.saturating_sub(last_total_earnings)); @@ -1708,11 +1668,12 @@ impl Pallet { /// This will persist updates for the reward pool to storage. But it will *not* persist updates /// to the `delegator` or `bonded_pool` to storage, that is the responsibility of the caller. fn do_reward_payout( - delegator_id: T::AccountId, + delegator_account: T::AccountId, delegator: &mut Delegator, bonded_pool: &mut BondedPool, ) -> DispatchResult { - let mut reward_pool = RewardPools::::get(&delegator.bonded_pool_account) + debug_assert_eq!(delegator.pool_id, bonded_pool.id); + let mut reward_pool = RewardPools::::get(delegator.pool_id) .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; let delegator_payout = @@ -1720,20 +1681,20 @@ impl Pallet { // Transfer payout to the delegator. T::Currency::transfer( - &reward_pool.account, - &delegator_id, + &bonded_pool.reward_account(), + &delegator_account, delegator_payout, ExistenceRequirement::AllowDeath, )?; Self::deposit_event(Event::::PaidOut { - delegator: delegator_id.clone(), - pool: delegator.bonded_pool_account.clone(), + delegator: delegator_account, + pool_id: delegator.pool_id, payout: delegator_payout, }); // Write the reward pool to storage - RewardPools::insert(&delegator.bonded_pool_account, reward_pool); + RewardPools::insert(&delegator.pool_id, reward_pool); Ok(()) } @@ -1742,18 +1703,22 @@ impl Pallet { impl OnStakerSlash> for Pallet { fn on_slash( pool_account: &T::AccountId, - _slashed_bonded: BalanceOf, // Bonded balance is always read directly from staking. + // Bonded balance is always read directly from staking, therefore we need not update + // anything here. + _slashed_bonded: BalanceOf, slashed_unlocking: &BTreeMap>, ) { - let mut sub_pools = match SubPoolsStorage::::get(pool_account) { - Some(sub_pools) => sub_pools, - None => return, - }; - for (era, slashed_balance) in slashed_unlocking.iter() { - if let Some(pool) = sub_pools.with_era.get_mut(era) { - pool.balance = *slashed_balance + if let Some(pool_id) = ReversePoolIdLookup::::get(pool_account) { + let mut sub_pools = match SubPoolsStorage::::get(pool_id).defensive() { + Some(sub_pools) => sub_pools, + None => return, + }; + for (era, slashed_balance) in slashed_unlocking.iter() { + if let Some(pool) = sub_pools.with_era.get_mut(era) { + pool.balance = *slashed_balance + } } + SubPoolsStorage::::insert(pool_id, sub_pools); } - SubPoolsStorage::::insert(pool_account.clone(), sub_pools); } } diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 2d74e6464afae..0216df5a01faf 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -1,15 +1,20 @@ use super::*; use crate::{self as pools}; -use frame_support::{assert_ok, parameter_types}; +use frame_support::{assert_ok, parameter_types, PalletId}; use frame_system::RawOrigin; -pub type AccountId = u32; +pub type AccountId = u128; pub type Balance = u128; -/// _Stash_ of the pool that gets created by the [`ExtBuilder`]. -pub const PRIMARY_ACCOUNT: u32 = 1552898353; -/// Reward destination of the pool that gets created by the [`ExtBuilder`]. -pub const REWARDS_ACCOUNT: u32 = 3802151463; +// Ext builder creates a pool with id 1. +pub fn default_bonded_account() -> AccountId { + Pools::create_bonded_account(1) +} + +// Ext builder creates a pool with id 1. +pub fn default_reward_account() -> AccountId { + Pools::create_reward_account(1) +} parameter_types! { pub static CurrentEra: EraIndex = 0; @@ -153,8 +158,8 @@ impl Convert for U256ToBalance { parameter_types! { pub static PostUnbondingPoolsWindow: u32 = 2; pub static MaxMetadataLen: u32 = 2; + pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); } - impl pools::Config for Runtime { type Event = Event; type WeightInfo = (); @@ -163,6 +168,7 @@ impl pools::Config for Runtime { type U256ToBalance = U256ToBalance; type StakingInterface = StakingMock; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; + type PalletId = PoolsPalletId; type MaxMetadataLen = MaxMetadataLen; } @@ -214,14 +220,10 @@ impl ExtBuilder { Balances::make_free_balance_be(&10, amount_to_bond * 2); 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.delegators { Balances::make_free_balance_be(&account_id, bonded * 2); - - assert_ok!(Pools::join( - RawOrigin::Signed(account_id).into(), - bonded, - PRIMARY_ACCOUNT - )); + assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, last_pool)); } }); @@ -242,8 +244,8 @@ impl ExtBuilder { } } -pub(crate) fn unsafe_set_state(pool_account: &AccountId, state: PoolState) -> Result<(), ()> { - BondedPools::::try_mutate(pool_account, |maybe_bonded_pool| { +pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) -> Result<(), ()> { + BondedPools::::try_mutate(pool_id, |maybe_bonded_pool| { maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { bonded_pool.state = state; }) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index fdc7925f291c2..51c1d33e3a1ca 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1,8 +1,5 @@ use super::*; -use crate::mock::{ - unsafe_set_state, Balance, Balances, CurrentEra, ExistentialDeposit, ExtBuilder, Nominations, - Origin, Pools, Runtime, StakingMock, PRIMARY_ACCOUNT, REWARDS_ACCOUNT, UNBONDING_BALANCE_MAP, -}; +use crate::mock::*; use frame_support::{assert_noop, assert_ok}; macro_rules! sub_pools_with_era { @@ -13,6 +10,9 @@ macro_rules! sub_pools_with_era { }}; } +pub const DEFAULT_ROLES: PoolRoles = + PoolRoles { depositor: 10, root: 900, nominator: 901, state_toggler: 902 }; + #[test] fn test_setup_works() { ExtBuilder::default().build_and_execute(|| { @@ -22,32 +22,27 @@ fn test_setup_works() { assert_eq!(Delegators::::count(), 1); assert_eq!(StakingMock::bonding_duration(), 3); + let last_pool = LastPoolId::::get(); assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool::::get(last_pool).unwrap(), BondedPool:: { - depositor: 10, - state: PoolState::Open, - points: 10, - account: PRIMARY_ACCOUNT, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 1, + id: last_pool, + inner: BondedPoolInner { + state: PoolState::Open, + points: 10, + delegator_counter: 1, + roles: DEFAULT_ROLES + }, } ); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), - RewardPool:: { - balance: 0, - points: 0.into(), - total_earnings: 0, - account: REWARDS_ACCOUNT - } + RewardPools::::get(last_pool).unwrap(), + RewardPool:: { balance: 0, points: 0.into(), total_earnings: 0 } ); assert_eq!( Delegators::::get(10).unwrap(), Delegator:: { - bonded_pool_account: PRIMARY_ACCOUNT, + pool_id: last_pool, points: 10, reward_pool_total_earnings: 0, unbonding_era: None @@ -71,51 +66,50 @@ mod bonded_pool { #[test] fn points_to_issue_works() { let mut bonded_pool = BondedPool:: { - depositor: 10, - state: PoolState::Open, - points: 100, - account: 123, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 1, + id: 123123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + delegator_counter: 1, + roles: DEFAULT_ROLES, + }, }; // 1 points : 1 balance ratio - StakingMock::set_bonded_balance(123, 100); + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); assert_eq!(bonded_pool.points_to_issue(10), 10); assert_eq!(bonded_pool.points_to_issue(0), 0); // 2 points : 1 balance ratio - StakingMock::set_bonded_balance(123, 50); + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 50); assert_eq!(bonded_pool.points_to_issue(10), 20); // 1 points : 2 balance ratio - StakingMock::set_bonded_balance(123, 100); + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 50; assert_eq!(bonded_pool.points_to_issue(10), 5); // 100 points : 0 balance ratio - StakingMock::set_bonded_balance(123, 0); + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); bonded_pool.points = 100; assert_eq!(bonded_pool.points_to_issue(10), 100 * 10); // 0 points : 100 balance - StakingMock::set_bonded_balance(123, 100); + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 100; assert_eq!(bonded_pool.points_to_issue(10), 10); // 10 points : 3 balance ratio - StakingMock::set_bonded_balance(123, 30); + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 30); assert_eq!(bonded_pool.points_to_issue(10), 33); // 2 points : 3 balance ratio - StakingMock::set_bonded_balance(123, 300); + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 300); bonded_pool.points = 200; assert_eq!(bonded_pool.points_to_issue(10), 6); // 4 points : 9 balance ratio - StakingMock::set_bonded_balance(123, 900); + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 900); bonded_pool.points = 400; assert_eq!(bonded_pool.points_to_issue(90), 40); } @@ -124,16 +118,16 @@ mod bonded_pool { fn balance_to_unbond_works() { // 1 balance : 1 points ratio let mut bonded_pool = BondedPool:: { - depositor: 10, - state: PoolState::Open, - points: 100, - account: 123, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 1, + id: 123123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + delegator_counter: 1, + roles: DEFAULT_ROLES, + }, }; - StakingMock::set_bonded_balance(123, 100); + + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); assert_eq!(bonded_pool.balance_to_unbond(10), 10); assert_eq!(bonded_pool.balance_to_unbond(0), 0); @@ -142,27 +136,27 @@ mod bonded_pool { assert_eq!(bonded_pool.balance_to_unbond(10), 20); // 100 balance : 0 points ratio - StakingMock::set_bonded_balance(123, 0); + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); bonded_pool.points = 0; assert_eq!(bonded_pool.balance_to_unbond(10), 0); // 0 balance : 100 points ratio - StakingMock::set_bonded_balance(123, 0); + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); bonded_pool.points = 100; assert_eq!(bonded_pool.balance_to_unbond(10), 0); // 10 balance : 3 points ratio - StakingMock::set_bonded_balance(123, 100); + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 30; assert_eq!(bonded_pool.balance_to_unbond(10), 33); // 2 balance : 3 points ratio - StakingMock::set_bonded_balance(123, 200); + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 200); bonded_pool.points = 300; assert_eq!(bonded_pool.balance_to_unbond(10), 6); // 4 balance : 9 points ratio - StakingMock::set_bonded_balance(123, 400); + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 400); bonded_pool.points = 900; assert_eq!(bonded_pool.balance_to_unbond(90), 40); } @@ -171,33 +165,32 @@ mod bonded_pool { fn ok_to_join_with_works() { ExtBuilder::default().build_and_execute(|| { let pool = BondedPool:: { - depositor: 10, - state: PoolState::Open, - points: 100, - account: 123, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 1, + id: 123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + delegator_counter: 1, + roles: DEFAULT_ROLES, + }, }; // Simulate a 100% slashed pool - StakingMock::set_bonded_balance(123, 0); + StakingMock::set_bonded_balance(pool.bonded_account(), 0); assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); // Simulate a 89% - StakingMock::set_bonded_balance(123, 11); + StakingMock::set_bonded_balance(pool.bonded_account(), 11); assert_ok!(pool.ok_to_join()); // Simulate a 90% slashed pool - StakingMock::set_bonded_balance(123, 10); + StakingMock::set_bonded_balance(pool.bonded_account(), 10); assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); - StakingMock::set_bonded_balance(123, Balance::MAX / 10); + StakingMock::set_bonded_balance(pool.bonded_account(), Balance::MAX / 10); // New bonded balance would be over 1/10th of Balance type assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); // and a sanity check - StakingMock::set_bonded_balance(123, Balance::MAX / 10 - 1); + StakingMock::set_bonded_balance(pool.bonded_account(), Balance::MAX / 10 - 1); assert_ok!(pool.ok_to_join()); }); } @@ -280,6 +273,7 @@ mod unbond_pool { assert_eq!(unbond_pool.balance_to_unbond(90), 40); } } + mod sub_pools { use super::*; @@ -358,14 +352,13 @@ mod join { #[test] fn join_works() { let bonded = |points, delegator_counter| BondedPool:: { - depositor: 10, - state: PoolState::Open, - points, - account: PRIMARY_ACCOUNT, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter, + id: 1, + inner: BondedPoolInner { + state: PoolState::Open, + points, + delegator_counter, + roles: DEFAULT_ROLES, + }, }; ExtBuilder::default().build_and_execute(|| { // Given @@ -373,104 +366,100 @@ mod join { assert!(!Delegators::::contains_key(&11)); // When - assert_ok!(Pools::join(Origin::signed(11), 2, PRIMARY_ACCOUNT)); + assert_ok!(Pools::join(Origin::signed(11), 2, 1)); // then assert_eq!( Delegators::::get(&11).unwrap(), Delegator:: { - bonded_pool_account: PRIMARY_ACCOUNT, + pool_id: 1, points: 2, reward_pool_total_earnings: 0, unbonding_era: None } ); - assert_eq!(BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), bonded(12, 2)); + assert_eq!(BondedPool::::get(1).unwrap(), bonded(12, 2)); // Given // The bonded balance is slashed in half - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 6); + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 6); + // And Balances::make_free_balance_be(&12, ExistentialDeposit::get() + 12); assert!(!Delegators::::contains_key(&12)); // When - assert_ok!(Pools::join(Origin::signed(12), 12, PRIMARY_ACCOUNT)); + assert_ok!(Pools::join(Origin::signed(12), 12, 1)); // Then assert_eq!( Delegators::::get(&12).unwrap(), Delegator:: { - bonded_pool_account: PRIMARY_ACCOUNT, + pool_id: 1, points: 24, reward_pool_total_earnings: 0, unbonding_era: None } ); - assert_eq!(BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), bonded(12 + 24, 3)); + assert_eq!(BondedPool::::get(1).unwrap(), bonded(12 + 24, 3)); }); } #[test] fn join_errors_correctly() { ExtBuilder::default().build_and_execute_no_checks(|| { + // 10 is already part of the default pool created. + assert_eq!(Delegators::::get(&10).unwrap().pool_id, 1); + assert_noop!( - Pools::join(Origin::signed(10), 420, PRIMARY_ACCOUNT), + Pools::join(Origin::signed(10), 420, 123), Error::::AccountBelongsToOtherPool ); - assert_noop!(Pools::join(Origin::signed(11), 420, 420), Error::::PoolNotFound); + assert_noop!(Pools::join(Origin::signed(11), 420, 123), Error::::PoolNotFound); // Force the pools bonded balance to 0, simulating a 100% slash - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 0); - assert_noop!( - Pools::join(Origin::signed(11), 420, PRIMARY_ACCOUNT), - Error::::OverflowRisk - ); + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 0); + assert_noop!(Pools::join(Origin::signed(11), 420, 1), Error::::OverflowRisk); // Given a mocked bonded pool BondedPool:: { - depositor: 10, - state: PoolState::Open, - points: 100, - account: 123, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 1, + id: 123, + inner: BondedPoolInner { + delegator_counter: 1, + state: PoolState::Open, + points: 100, + roles: DEFAULT_ROLES, + }, } .put(); + // and reward pool RewardPools::::insert( 123, RewardPool:: { - account: 1123, balance: Zero::zero(), total_earnings: Zero::zero(), - points: U256::from(0), + points: U256::from(0u32), }, ); // Force the points:balance ratio to 100/10 - StakingMock::set_bonded_balance(123, 10); + StakingMock::set_bonded_balance(Pools::create_bonded_account(123), 10); assert_noop!(Pools::join(Origin::signed(11), 420, 123), Error::::OverflowRisk); - StakingMock::set_bonded_balance(123, Balance::MAX / 10); + StakingMock::set_bonded_balance(Pools::create_bonded_account(123), Balance::MAX / 10); // Balance is gt 1/10 of Balance::MAX assert_noop!(Pools::join(Origin::signed(11), 5, 123), Error::::OverflowRisk); - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 10); + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 10); + // Cannot join a pool that isn't open - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); - assert_noop!( - Pools::join(Origin::signed(11), 10, PRIMARY_ACCOUNT), - Error::::NotOpen - ); - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); - assert_noop!( - Pools::join(Origin::signed(11), 10, PRIMARY_ACCOUNT), - Error::::NotOpen - ); + unsafe_set_state(123, PoolState::Blocked).unwrap(); + assert_noop!(Pools::join(Origin::signed(11), 10, 123), Error::::NotOpen); + + unsafe_set_state(123, PoolState::Destroying).unwrap(); + assert_noop!(Pools::join(Origin::signed(11), 10, 123), Error::::NotOpen); // Given MinJoinBond::::put(100); @@ -486,17 +475,17 @@ mod join { #[test] #[should_panic = "Defensive failure has been triggered!"] fn join_panics_when_reward_pool_not_found() { + // TODO: but we should fail defensively.. ExtBuilder::default().build_and_execute(|| { - StakingMock::set_bonded_balance(123, 100); + StakingMock::set_bonded_balance(Pools::create_bonded_account(123), 100); BondedPool:: { - depositor: 10, - state: PoolState::Open, - points: 100, - account: 123, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 1, + id: 123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + delegator_counter: 1, + roles: DEFAULT_ROLES, + }, } .put(); let _ = Pools::join(Origin::signed(11), 420, 123); @@ -512,24 +501,23 @@ mod join { let account = i + 100; Balances::make_free_balance_be(&account, 100 + Balances::minimum_balance()); - assert_ok!(Pools::join(Origin::signed(account), 100, PRIMARY_ACCOUNT)); + assert_ok!(Pools::join(Origin::signed(account), 100, 1)); } Balances::make_free_balance_be(&103, 100 + Balances::minimum_balance()); // Then - assert_noop!( - Pools::join(Origin::signed(103), 100, PRIMARY_ACCOUNT), - Error::::MaxDelegators - ); + assert_noop!(Pools::join(Origin::signed(103), 100, 1), Error::::MaxDelegators); // Given assert_eq!(Delegators::::count(), 3); assert_eq!(MaxDelegators::::get(), Some(4)); + Balances::make_free_balance_be(&104, 100 + Balances::minimum_balance()); assert_ok!(Pools::create(Origin::signed(104), 100, 104, 104, 104)); + let pool_account = BondedPools::::iter() - .find(|(_, bonded_pool)| bonded_pool.depositor == 104) + .find(|(_, bonded_pool)| bonded_pool.roles.depositor == 104) .map(|(pool_account, _)| pool_account) .unwrap(); @@ -546,16 +534,11 @@ mod claim_payout { use super::*; fn del(points: Balance, reward_pool_total_earnings: Balance) -> Delegator { - Delegator { - bonded_pool_account: PRIMARY_ACCOUNT, - points, - reward_pool_total_earnings, - unbonding_era: None, - } + Delegator { pool_id: 1, points, reward_pool_total_earnings, unbonding_era: None } } fn rew(balance: Balance, points: u32, total_earnings: Balance) -> RewardPool { - RewardPool { balance, points: points.into(), total_earnings, account: REWARDS_ACCOUNT } + RewardPool { balance, points: points.into(), total_earnings } } #[test] @@ -567,8 +550,9 @@ mod claim_payout { Balances::make_free_balance_be(&10, 0); Balances::make_free_balance_be(&40, 0); Balances::make_free_balance_be(&50, 0); + let reward_account = Pools::create_reward_account(1); // and the reward pool has earned 100 in rewards - Balances::make_free_balance_be(&REWARDS_ACCOUNT, 100); + Balances::make_free_balance_be(&reward_account, 100); // When assert_ok!(Pools::claim_payout(Origin::signed(10))); @@ -578,11 +562,11 @@ mod claim_payout { // balance assert_eq!(Delegators::::get(10).unwrap(), del(10, 100)); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&1).unwrap(), rew(90, 100 * 100 - 100 * 10, 100) ); assert_eq!(Balances::free_balance(&10), 10); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 90); + assert_eq!(Balances::free_balance(&reward_account), 90); // When assert_ok!(Pools::claim_payout(Origin::signed(40))); @@ -591,11 +575,11 @@ mod claim_payout { // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance assert_eq!(Delegators::::get(40).unwrap(), del(40, 100)); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&1).unwrap(), rew(50, 9_000 - 100 * 40, 100) ); assert_eq!(Balances::free_balance(&40), 40); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 50); + assert_eq!(Balances::free_balance(&reward_account), 50); // When assert_ok!(Pools::claim_payout(Origin::signed(50))); @@ -603,12 +587,12 @@ mod claim_payout { // Then // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance assert_eq!(Delegators::::get(50).unwrap(), del(50, 100)); - assert_eq!(RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(0, 0, 100)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 100)); assert_eq!(Balances::free_balance(&50), 50); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); + assert_eq!(Balances::free_balance(&reward_account), 0); // Given the reward pool has some new rewards - Balances::make_free_balance_be(&REWARDS_ACCOUNT, 50); + Balances::make_free_balance_be(&reward_account, 50); // When assert_ok!(Pools::claim_payout(Origin::signed(10))); @@ -616,12 +600,9 @@ mod claim_payout { // Then // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance assert_eq!(Delegators::::get(10).unwrap(), del(10, 150)); - assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), - rew(45, 5_000 - 50 * 10, 150) - ); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(45, 5_000 - 50 * 10, 150)); assert_eq!(Balances::free_balance(&10), 10 + 5); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 45); + assert_eq!(Balances::free_balance(&reward_account), 45); // When assert_ok!(Pools::claim_payout(Origin::signed(40))); @@ -630,16 +611,13 @@ mod claim_payout { // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool // balance assert_eq!(Delegators::::get(40).unwrap(), del(40, 150)); - assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), - rew(25, 4_500 - 50 * 40, 150) - ); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(25, 4_500 - 50 * 40, 150)); assert_eq!(Balances::free_balance(&40), 40 + 20); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 25); + assert_eq!(Balances::free_balance(&reward_account), 25); // Given del 50 hasn't claimed and the reward pools has just earned 50 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 50)); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 75); + assert_ok!(Balances::mutate_account(&reward_account, |a| a.free += 50)); + assert_eq!(Balances::free_balance(&reward_account), 75); // When assert_ok!(Pools::claim_payout(Origin::signed(50))); @@ -649,7 +627,7 @@ mod claim_payout { // pool balance assert_eq!(Delegators::::get(50).unwrap(), del(50, 200)); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&1).unwrap(), rew( 25, // old pool points + points from new earnings - del points. @@ -661,7 +639,7 @@ mod claim_payout { ) ); assert_eq!(Balances::free_balance(&50), 50 + 50); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 25); + assert_eq!(Balances::free_balance(&reward_account), 25); // When assert_ok!(Pools::claim_payout(Origin::signed(10))); @@ -669,16 +647,13 @@ mod claim_payout { // Then // We expect a payout of 5 assert_eq!(Delegators::::get(10).unwrap(), del(10, 200)); - assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), - rew(20, 2_500 - 10 * 50, 200) - ); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(20, 2_500 - 10 * 50, 200)); assert_eq!(Balances::free_balance(&10), 15 + 5); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 20); + assert_eq!(Balances::free_balance(&reward_account), 20); // Given del 40 hasn't claimed and the reward pool has just earned 400 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 400)); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 420); + assert_ok!(Balances::mutate_account(&reward_account, |a| a.free += 400)); + assert_eq!(Balances::free_balance(&reward_account), 420); // When assert_ok!(Pools::claim_payout(Origin::signed(10))); @@ -687,7 +662,7 @@ mod claim_payout { // We expect a payout of 40 assert_eq!(Delegators::::get(10).unwrap(), del(10, 600)); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&1).unwrap(), rew( 380, // old pool points + points from new earnings - del points @@ -699,11 +674,11 @@ mod claim_payout { ) ); assert_eq!(Balances::free_balance(&10), 20 + 40); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 380); + assert_eq!(Balances::free_balance(&reward_account), 380); // Given del 40 + del 50 haven't claimed and the reward pool has earned 20 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 20)); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 400); + assert_ok!(Balances::mutate_account(&reward_account, |a| a.free += 20)); + assert_eq!(Balances::free_balance(&reward_account), 400); // When assert_ok!(Pools::claim_payout(Origin::signed(10))); @@ -713,11 +688,11 @@ mod claim_payout { // balance assert_eq!(Delegators::::get(10).unwrap(), del(10, 620)); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&1).unwrap(), rew(398, (38_000 + 20 * 100) - 10 * 20, 620) ); assert_eq!(Balances::free_balance(&10), 60 + 2); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 398); + assert_eq!(Balances::free_balance(&reward_account), 398); // When assert_ok!(Pools::claim_payout(Origin::signed(40))); @@ -727,11 +702,11 @@ mod claim_payout { // pool balance assert_eq!(Delegators::::get(40).unwrap(), del(40, 620)); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&1).unwrap(), rew(210, 39_800 - 40 * 470, 620) ); assert_eq!(Balances::free_balance(&40), 60 + 188); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 210); + assert_eq!(Balances::free_balance(&reward_account), 210); // When assert_ok!(Pools::claim_payout(Origin::signed(50))); @@ -740,25 +715,26 @@ mod claim_payout { // Expect payout of 210: (21,000 / 21,000) * 210 assert_eq!(Delegators::::get(50).unwrap(), del(50, 620)); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&1).unwrap(), rew(0, 21_000 - 50 * 420, 620) ); assert_eq!(Balances::free_balance(&50), 100 + 210); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); + assert_eq!(Balances::free_balance(&reward_account), 0); }); } #[test] fn do_reward_payout_correctly_sets_pool_state_to_destroying() { ExtBuilder::default().build_and_execute(|| { - let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); - let mut reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); + let reward_account = Pools::create_reward_account(1); // --- reward_pool.total_earnings saturates // Given - Balances::make_free_balance_be(&reward_pool.account, Balance::MAX); + Balances::make_free_balance_be(&reward_account, Balance::MAX); // When assert_ok!(Pools::do_reward_payout(10, &mut delegator, &mut bonded_pool)); @@ -766,18 +742,17 @@ mod claim_payout { // Then assert!(bonded_pool.is_destroying()); - // -- current_points saturates (reward_pool.points + new_earnings * bonded_pool.points) // Given - let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); // Force new_earnings * bonded_pool.points == 100 - Balances::make_free_balance_be(&reward_pool.account, 10); + Balances::make_free_balance_be(&reward_account, 10); assert_eq!(bonded_pool.points, 10); // Force reward_pool.points == U256::MAX - new_earnings * bonded_pool.points - reward_pool.points = U256::MAX - U256::from(100); - RewardPools::::insert(PRIMARY_ACCOUNT, reward_pool.clone()); + reward_pool.points = U256::MAX - U256::from(100u32); + RewardPools::::insert(1, reward_pool.clone()); // When assert_ok!(Pools::do_reward_payout(10, &mut delegator, &mut bonded_pool)); @@ -790,8 +765,8 @@ mod claim_payout { #[test] fn calculate_delegator_payout_errors_if_a_delegator_is_unbonding() { ExtBuilder::default().build_and_execute(|| { - let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); - let mut reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); delegator.unbonding_era = Some(0 + 3); @@ -811,9 +786,10 @@ mod claim_payout { let del = |reward_pool_total_earnings| del(10, reward_pool_total_earnings); ExtBuilder::default().build_and_execute(|| { - let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); - let mut reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); + let reward_account = Pools::create_reward_account(1); // Given no rewards have been earned // When @@ -830,7 +806,7 @@ mod claim_payout { assert_eq!(reward_pool, rew(0, 0, 0)); // Given the pool has earned some rewards for the first time - Balances::make_free_balance_be(&reward_pool.account, 5); + Balances::make_free_balance_be(&reward_account, 5); // When let payout = Pools::calculate_delegator_payout( @@ -846,7 +822,7 @@ mod claim_payout { assert_eq!(delegator, del(5)); // Given the pool has earned rewards again - Balances::make_free_balance_be(&REWARDS_ACCOUNT, 10); + Balances::make_free_balance_be(&reward_account, 10); // When let payout = Pools::calculate_delegator_payout( @@ -862,7 +838,7 @@ mod claim_payout { assert_eq!(delegator, del(15)); // Given the pool has earned no new rewards - Balances::make_free_balance_be(&REWARDS_ACCOUNT, 0); + Balances::make_free_balance_be(&reward_account, 0); // When let payout = Pools::calculate_delegator_payout( @@ -884,8 +860,8 @@ mod claim_payout { ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); - let mut reward_pool = RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); // Delegator with 10 points let mut del_10 = Delegators::::get(10).unwrap(); // Delegator with 40 points @@ -897,7 +873,7 @@ mod claim_payout { assert_eq!(del_50.points + del_40.points + del_10.points, 100); assert_eq!(bonded_pool.points, 100); // and the reward pool has earned 100 in rewards - Balances::make_free_balance_be(&REWARDS_ACCOUNT, 100); + Balances::make_free_balance_be(&default_reward_account(), 100); // When let payout = Pools::calculate_delegator_payout( @@ -912,7 +888,7 @@ mod claim_payout { assert_eq!(del_10, del(10, 100)); assert_eq!(reward_pool, rew(90, 100 * 100 - 100 * 10, 100)); // Mock the reward pool transferring the payout to del_10 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 10)); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 10)); // When let payout = Pools::calculate_delegator_payout( @@ -935,7 +911,7 @@ mod claim_payout { ) ); // Mock the reward pool transferring the payout to del_40 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 40)); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 40)); // When let payout = Pools::calculate_delegator_payout( @@ -950,10 +926,10 @@ mod claim_payout { assert_eq!(del_50, del(50, 100)); assert_eq!(reward_pool, rew(0, 0, 100)); // Mock the reward pool transferring the payout to del_50 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 50)); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 50)); // Given the reward pool has some new rewards - Balances::make_free_balance_be(&REWARDS_ACCOUNT, 50); + Balances::make_free_balance_be(&default_reward_account(), 50); // When let payout = Pools::calculate_delegator_payout( @@ -968,7 +944,7 @@ mod claim_payout { assert_eq!(del_10, del(10, 150)); assert_eq!(reward_pool, rew(45, 5_000 - 50 * 10, 150)); // Mock the reward pool transferring the payout to del_10 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 5)); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 5)); // When let payout = Pools::calculate_delegator_payout( @@ -982,11 +958,11 @@ mod claim_payout { assert_eq!(payout, 20); // (2,000 del virtual points / 4,500 pool points) * 45 pool balance assert_eq!(del_40, del(40, 150)); assert_eq!(reward_pool, rew(25, 4_500 - 50 * 40, 150)); - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 20)); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 20)); // Given del_50 hasn't claimed and the reward pools has just earned 50 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 50)); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 75); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + assert_eq!(Balances::free_balance(&default_reward_account()), 75); // When let payout = Pools::calculate_delegator_payout( @@ -1012,7 +988,7 @@ mod claim_payout { ) ); // Mock the reward pool transferring the payout to del_50 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 50)); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 50)); // When let payout = Pools::calculate_delegator_payout( @@ -1027,11 +1003,11 @@ mod claim_payout { assert_eq!(del_10, del(10, 200)); assert_eq!(reward_pool, rew(20, 2_500 - 10 * 50, 200)); // Mock the reward pool transferring the payout to del_10 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 5)); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 5)); // Given del_40 hasn't claimed and the reward pool has just earned 400 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 400)); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 420); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); + assert_eq!(Balances::free_balance(&default_reward_account()), 420); // When let payout = Pools::calculate_delegator_payout( @@ -1057,11 +1033,11 @@ mod claim_payout { ) ); // Mock the reward pool transferring the payout to del_10 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 40)); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 40)); // Given del_40 + del_50 haven't claimed and the reward pool has earned 20 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 20)); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 400); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); + assert_eq!(Balances::free_balance(&default_reward_account()), 400); // When let payout = Pools::calculate_delegator_payout( @@ -1076,7 +1052,7 @@ mod claim_payout { assert_eq!(del_10, del(10, 620)); assert_eq!(reward_pool, rew(398, (38_000 + 20 * 100) - 10 * 20, 620)); // Mock the reward pool transferring the payout to del_10 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 2)); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 2)); // When let payout = Pools::calculate_delegator_payout( @@ -1091,7 +1067,7 @@ mod claim_payout { assert_eq!(del_40, del(40, 620)); assert_eq!(reward_pool, rew(210, 39_800 - 40 * 470, 620)); // Mock the reward pool transferring the payout to del_10 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free -= 188)); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 188)); // When let payout = Pools::calculate_delegator_payout( @@ -1113,7 +1089,7 @@ mod claim_payout { ExtBuilder::default() .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut bonded_pool = BondedPool::::get(1).unwrap(); // Given the bonded pool has 100 points assert_eq!(bonded_pool.points, 100); @@ -1122,7 +1098,7 @@ mod claim_payout { Balances::make_free_balance_be(&40, 0); Balances::make_free_balance_be(&50, 0); // and the reward pool has earned 100 in rewards - Balances::make_free_balance_be(&REWARDS_ACCOUNT, 100); + Balances::make_free_balance_be(&default_reward_account(), 100); let mut del_10 = Delegators::get(10).unwrap(); let mut del_40 = Delegators::get(40).unwrap(); @@ -1136,11 +1112,11 @@ mod claim_payout { // balance assert_eq!(del_10, del(10, 100)); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&1).unwrap(), rew(90, 100 * 100 - 100 * 10, 100) ); assert_eq!(Balances::free_balance(&10), 10); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 90); + assert_eq!(Balances::free_balance(&default_reward_account()), 90); // When assert_ok!(Pools::do_reward_payout(40, &mut del_40, &mut bonded_pool)); @@ -1149,11 +1125,11 @@ mod claim_payout { // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance assert_eq!(del_40, del(40, 100)); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(&1).unwrap(), rew(50, 9_000 - 100 * 40, 100) ); assert_eq!(Balances::free_balance(&40), 40); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 50); + assert_eq!(Balances::free_balance(&default_reward_account()), 50); // When assert_ok!(Pools::do_reward_payout(50, &mut del_50, &mut bonded_pool)); @@ -1161,12 +1137,12 @@ mod claim_payout { // Then // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance assert_eq!(del_50, del(50, 100)); - assert_eq!(RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), rew(0, 0, 100)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 100)); assert_eq!(Balances::free_balance(&50), 50); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); + assert_eq!(Balances::free_balance(&default_reward_account()), 0); // Given the reward pool has some new rewards - Balances::make_free_balance_be(&REWARDS_ACCOUNT, 50); + Balances::make_free_balance_be(&default_reward_account(), 50); // When assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); @@ -1174,12 +1150,9 @@ mod claim_payout { // Then // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance assert_eq!(del_10, del(10, 150)); - assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), - rew(45, 5_000 - 50 * 10, 150) - ); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(45, 5_000 - 50 * 10, 150)); assert_eq!(Balances::free_balance(&10), 10 + 5); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 45); + assert_eq!(Balances::free_balance(&default_reward_account()), 45); // When assert_ok!(Pools::do_reward_payout(40, &mut del_40, &mut bonded_pool)); @@ -1188,16 +1161,13 @@ mod claim_payout { // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool // balance assert_eq!(del_40, del(40, 150)); - assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), - rew(25, 4_500 - 50 * 40, 150) - ); + assert_eq!(RewardPools::::get(1).unwrap(), rew(25, 4_500 - 50 * 40, 150)); assert_eq!(Balances::free_balance(&40), 40 + 20); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 25); + assert_eq!(Balances::free_balance(&default_reward_account()), 25); // Given del 50 hasn't claimed and the reward pools has just earned 50 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 50)); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 75); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + assert_eq!(Balances::free_balance(&default_reward_account()), 75); // When assert_ok!(Pools::do_reward_payout(50, &mut del_50, &mut bonded_pool)); @@ -1207,7 +1177,7 @@ mod claim_payout { // pool balance assert_eq!(del_50, del(50, 200)); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(1).unwrap(), rew( 25, // old pool points + points from new earnings - del points. @@ -1219,7 +1189,7 @@ mod claim_payout { ) ); assert_eq!(Balances::free_balance(&50), 50 + 50); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 25); + assert_eq!(Balances::free_balance(&default_reward_account()), 25); // When assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); @@ -1227,16 +1197,13 @@ mod claim_payout { // Then // We expect a payout of 5 assert_eq!(del_10, del(10, 200)); - assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), - rew(20, 2_500 - 10 * 50, 200) - ); + assert_eq!(RewardPools::::get(1).unwrap(), rew(20, 2_500 - 10 * 50, 200)); assert_eq!(Balances::free_balance(&10), 15 + 5); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 20); + assert_eq!(Balances::free_balance(&default_reward_account()), 20); // Given del 40 hasn't claimed and the reward pool has just earned 400 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 400)); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 420); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); + assert_eq!(Balances::free_balance(&default_reward_account()), 420); // When assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); @@ -1245,7 +1212,7 @@ mod claim_payout { // We expect a payout of 40 assert_eq!(del_10, del(10, 600)); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(1).unwrap(), rew( 380, // old pool points + points from new earnings - del points @@ -1257,11 +1224,11 @@ mod claim_payout { ) ); assert_eq!(Balances::free_balance(&10), 20 + 40); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 380); + assert_eq!(Balances::free_balance(&default_reward_account()), 380); // Given del 40 + del 50 haven't claimed and the reward pool has earned 20 - assert_ok!(Balances::mutate_account(&REWARDS_ACCOUNT, |a| a.free += 20)); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 400); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); + assert_eq!(Balances::free_balance(&default_reward_account()), 400); // When assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); @@ -1271,11 +1238,11 @@ mod claim_payout { // balance assert_eq!(del_10, del(10, 620)); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(1).unwrap(), rew(398, (38_000 + 20 * 100) - 10 * 20, 620) ); assert_eq!(Balances::free_balance(&10), 60 + 2); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 398); + assert_eq!(Balances::free_balance(&default_reward_account()), 398); // When assert_ok!(Pools::do_reward_payout(40, &mut del_40, &mut bonded_pool)); @@ -1285,11 +1252,11 @@ mod claim_payout { // pool balance assert_eq!(del_40, del(40, 620)); assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), + RewardPools::::get(1).unwrap(), rew(210, 39_800 - 40 * 470, 620) ); assert_eq!(Balances::free_balance(&40), 60 + 188); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 210); + assert_eq!(Balances::free_balance(&default_reward_account()), 210); // When assert_ok!(Pools::do_reward_payout(50, &mut del_50, &mut bonded_pool)); @@ -1297,19 +1264,16 @@ mod claim_payout { // Then // Expect payout of 210: (21,000 / 21,000) * 210 assert_eq!(del_50, del(50, 620)); - assert_eq!( - RewardPools::::get(&PRIMARY_ACCOUNT).unwrap(), - rew(0, 21_000 - 50 * 420, 620) - ); + assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 21_000 - 50 * 420, 620)); assert_eq!(Balances::free_balance(&50), 100 + 210); - assert_eq!(Balances::free_balance(&REWARDS_ACCOUNT), 0); + assert_eq!(Balances::free_balance(&default_reward_account()), 0); }); } #[test] fn do_reward_payout_errors_correctly() { ExtBuilder::default().build_and_execute(|| { - let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); // The only place this can return an error is with the balance transfer from the @@ -1319,7 +1283,7 @@ mod claim_payout { // Given delegator.points = 15; assert_eq!(bonded_pool.points, 10); - Balances::make_free_balance_be(&REWARDS_ACCOUNT, 10); + Balances::make_free_balance_be(&default_reward_account(), 10); // Then // Expect attempt payout of 15/10 * 10 when free balance is actually 10 @@ -1337,29 +1301,28 @@ mod unbond { #[test] fn unbond_other_of_1_works() { ExtBuilder::default().build_and_execute(|| { - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, + SubPoolsStorage::::get(1).unwrap().with_era, sub_pools_with_era! { 0 + 3 => UnbondPool:: { points: 10, balance: 10 }} ); assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool::::get(1).unwrap(), BondedPool { - depositor: 10, - state: PoolState::Destroying, - points: 0, - account: PRIMARY_ACCOUNT, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 1, + id: 1, + inner: BondedPoolInner { + state: PoolState::Destroying, + points: 0, + delegator_counter: 1, + roles: DEFAULT_ROLES, + } } ); - assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); + assert_eq!(StakingMock::bonded_balance(&default_bonded_account()).unwrap(), 0); }); } @@ -1369,58 +1332,57 @@ mod unbond { .add_delegators(vec![(40, 40), (550, 550)]) .build_and_execute(|| { // Given a slash from 600 -> 100 - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); + StakingMock::set_bonded_balance(default_bonded_account(), 100); // and unclaimed rewards of 600. - Balances::make_free_balance_be(&REWARDS_ACCOUNT, 600); + Balances::make_free_balance_be(&default_reward_account(), 600); // When assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); // Then assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, + SubPoolsStorage::::get(1).unwrap().with_era, sub_pools_with_era! { 0 + 3 => UnbondPool { points: 6, balance: 6 }} ); assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool::::get(1).unwrap(), BondedPool { - depositor: 10, - state: PoolState::Open, - points: 560, - account: PRIMARY_ACCOUNT, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 3, + id: 1, + inner: BondedPoolInner { + state: PoolState::Open, + points: 560, + delegator_counter: 3, + roles: DEFAULT_ROLES, + } } ); - assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 94); + + assert_eq!(StakingMock::bonded_balance(&default_bonded_account()).unwrap(), 94); assert_eq!(Delegators::::get(40).unwrap().unbonding_era, Some(0 + 3)); assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding // When - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); // Then assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, + SubPoolsStorage::::get(&1).unwrap().with_era, sub_pools_with_era! { 0 + 3 => UnbondPool { points: 98, balance: 98 }} ); assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool::::get(1).unwrap(), BondedPool { - depositor: 10, - state: PoolState::Destroying, - points: 10, - account: PRIMARY_ACCOUNT, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 3, + id: 1, + inner: BondedPoolInner { + state: PoolState::Destroying, + points: 10, + delegator_counter: 3, + roles: DEFAULT_ROLES + } } ); - assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 2); + assert_eq!(StakingMock::bonded_balance(&default_bonded_account()).unwrap(), 2); assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0 + 3)); assert_eq!(Balances::free_balance(&550), 550 + 550); @@ -1429,23 +1391,22 @@ mod unbond { // Then assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, + SubPoolsStorage::::get(1).unwrap().with_era, sub_pools_with_era! { 0 + 3 => UnbondPool { points: 100, balance: 100 }} ); assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool::::get(1).unwrap(), BondedPool { - depositor: 10, - state: PoolState::Destroying, - points: 0, - account: PRIMARY_ACCOUNT, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 3, + id: 1, + inner: BondedPoolInner { + state: PoolState::Destroying, + points: 0, + delegator_counter: 3, + roles: DEFAULT_ROLES + } } ); - assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); + assert_eq!(StakingMock::bonded_balance(&default_bonded_account()).unwrap(), 0); assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0 + 3)); assert_eq!(Balances::free_balance(&550), 550 + 550); }); @@ -1457,7 +1418,7 @@ mod unbond { // Given assert_eq!(StakingMock::bonding_duration(), 3); SubPoolsStorage::::insert( - PRIMARY_ACCOUNT, + 1, SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { @@ -1467,7 +1428,7 @@ mod unbond { }, }, ); - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying).unwrap(); // When let current_era = 1 + TotalUnbondingPools::::get(); @@ -1477,7 +1438,7 @@ mod unbond { // Then assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: UnbondPool { balance: 10 + 20, points: 100 + 20 }, with_era: sub_pools_with_era! { @@ -1496,11 +1457,11 @@ mod unbond { .add_delegators(vec![(100, 100), (200, 200)]) .build_and_execute(|| { // Given - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); - let bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); - assert_eq!(bonded_pool.root, 900); - assert_eq!(bonded_pool.nominator, 901); - assert_eq!(bonded_pool.state_toggler, 902); + unsafe_set_state(1, PoolState::Blocked).unwrap(); + let bonded_pool = BondedPool::::get(1).unwrap(); + assert_eq!(bonded_pool.roles.root, 900); + assert_eq!(bonded_pool.roles.nominator, 901); + assert_eq!(bonded_pool.roles.state_toggler, 902); // When the nominator trys to kick, then its a noop assert_noop!( @@ -1515,21 +1476,20 @@ mod unbond { assert_ok!(Pools::unbond_other(Origin::signed(902), 200)); assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool::::get(1).unwrap(), BondedPool { - root: 900, - nominator: 901, - state_toggler: 902, - account: PRIMARY_ACCOUNT, - depositor: 10, - state: PoolState::Blocked, - points: 10, // Only 10 points because 200 + 100 was unbonded - delegator_counter: 3, + id: 1, + inner: BondedPoolInner { + roles: DEFAULT_ROLES, + state: PoolState::Blocked, + points: 10, // Only 10 points because 200 + 100 was unbonded + delegator_counter: 3, + } } ); - assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 10); + assert_eq!(StakingMock::bonded_balance(&default_bonded_account()).unwrap(), 10); assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { @@ -1538,7 +1498,8 @@ mod unbond { } ); assert_eq!( - UNBONDING_BALANCE_MAP.with(|m| *m.borrow_mut().get(&PRIMARY_ACCOUNT).unwrap()), + UNBONDING_BALANCE_MAP + .with(|m| *m.borrow_mut().get(&default_bonded_account()).unwrap()), 100 + 200 ); }); @@ -1549,7 +1510,7 @@ mod unbond { // Scenarios where non-admin accounts can unbond others ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given the pool is blocked - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + unsafe_set_state(1, PoolState::Blocked).unwrap(); // A permissionless unbond attempt errors assert_noop!( @@ -1558,7 +1519,7 @@ mod unbond { ); // Given the pool is destroying - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying).unwrap(); // The depositor cannot be unbonded until they are the last delegator assert_noop!( @@ -1570,7 +1531,7 @@ mod unbond { assert_ok!(Pools::unbond_other(Origin::signed(420), 100)); // Given the pool is blocked - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + unsafe_set_state(1, PoolState::Blocked).unwrap(); // The depositor cannot be unbonded assert_noop!( @@ -1579,14 +1540,14 @@ mod unbond { ); // Given the pools is destroying - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying).unwrap(); // The depositor can be unbonded assert_ok!(Pools::unbond_other(Origin::signed(420), 10)); - assert_eq!(BondedPools::::get(&PRIMARY_ACCOUNT).unwrap().points, 0); + assert_eq!(BondedPools::::get(1).unwrap().points, 0); assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { @@ -1594,9 +1555,10 @@ mod unbond { } } ); - assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT).unwrap(), 0); + assert_eq!(StakingMock::bonded_balance(&default_bonded_account()).unwrap(), 0); assert_eq!( - UNBONDING_BALANCE_MAP.with(|m| *m.borrow_mut().get(&PRIMARY_ACCOUNT).unwrap()), + UNBONDING_BALANCE_MAP + .with(|m| *m.borrow_mut().get(&default_bonded_account()).unwrap()), 110 ); }); @@ -1613,7 +1575,7 @@ mod unbond { // Add the delegator let delegator = Delegator { - bonded_pool_account: 1, + pool_id: 2, points: 10, reward_pool_total_earnings: 0, unbonding_era: None, @@ -1629,21 +1591,20 @@ mod unbond { fn unbond_panics_when_reward_pool_not_found() { ExtBuilder::default().build_and_execute(|| { let delegator = Delegator { - bonded_pool_account: 1, + pool_id: 2, points: 10, reward_pool_total_earnings: 0, unbonding_era: None, }; Delegators::::insert(11, delegator); BondedPool:: { - depositor: 10, - state: PoolState::Open, - points: 10, - account: 1, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 1, + id: 1, + inner: BondedPoolInner { + state: PoolState::Open, + points: 10, + delegator_counter: 1, + roles: DEFAULT_ROLES, + }, } .put(); @@ -1659,19 +1620,19 @@ mod pool_withdraw_unbonded { fn pool_withdraw_unbonded_works() { ExtBuilder::default().build_and_execute(|| { // Given 10 unbond'ed directly against the pool account - assert_ok!(StakingMock::unbond(PRIMARY_ACCOUNT, 5)); + assert_ok!(StakingMock::unbond(default_bonded_account(), 5)); // and the pool account only has 10 balance - assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), Some(5)); - assert_eq!(StakingMock::locked_balance(&PRIMARY_ACCOUNT), Some(10)); - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 10); + assert_eq!(StakingMock::bonded_balance(&default_bonded_account()), Some(5)); + assert_eq!(StakingMock::locked_balance(&default_bonded_account()), Some(10)); + assert_eq!(Balances::free_balance(&default_bonded_account()), 10); // When - assert_ok!(Pools::pool_withdraw_unbonded(Origin::signed(10), PRIMARY_ACCOUNT, 0)); + assert_ok!(Pools::pool_withdraw_unbonded(Origin::signed(10), 1, 0)); // Then there unbonding balance is no longer locked - assert_eq!(StakingMock::bonded_balance(&PRIMARY_ACCOUNT), Some(5)); - assert_eq!(StakingMock::locked_balance(&PRIMARY_ACCOUNT), Some(5)); - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 10); + assert_eq!(StakingMock::bonded_balance(&default_bonded_account()), Some(5)); + assert_eq!(StakingMock::locked_balance(&default_bonded_account()), Some(5)); + assert_eq!(Balances::free_balance(&default_bonded_account()), 10); }); } } @@ -1688,15 +1649,15 @@ mod withdraw_unbonded_other { assert_eq!(StakingMock::bonding_duration(), 3); assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 600); + assert_eq!(Balances::free_balance(&default_bonded_account()), 600); let mut current_era = 1; CurrentEra::set(current_era); // In a new era, unbond the depositor - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); - let mut sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(); + let mut sub_pools = SubPoolsStorage::::get(1).unwrap(); // TODO: [now] in the future we could use StakingMock::unbond_era_for(current_era) // instead of current_era + 3. let unbond_pool = sub_pools.with_era.get_mut(&(current_era + 3)).unwrap(); @@ -1706,11 +1667,11 @@ mod withdraw_unbonded_other { // Simulate a slash to the pool with_era(current_era), decreasing the balance by // half unbond_pool.balance = 5; - SubPoolsStorage::::insert(PRIMARY_ACCOUNT, sub_pools); + SubPoolsStorage::::insert(1, sub_pools); // Update the equivalent of the unbonding chunks for the `StakingMock` UNBONDING_BALANCE_MAP - .with(|m| *m.borrow_mut().get_mut(&PRIMARY_ACCOUNT).unwrap() -= 5); - Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 595); + .with(|m| *m.borrow_mut().get_mut(&default_bonded_account()).unwrap() -= 5); + Balances::make_free_balance_be(&default_bonded_account(), 595); // Advance the current_era to ensure all `with_era` pools will be merged into // `no_era` pool @@ -1719,12 +1680,11 @@ mod withdraw_unbonded_other { // Simulate some other call to unbond that would merge `with_era` pools into // `no_era` - let sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT) - .unwrap() - .maybe_merge_pools(current_era + 3); - SubPoolsStorage::::insert(PRIMARY_ACCOUNT, sub_pools); + let sub_pools = + SubPoolsStorage::::get(1).unwrap().maybe_merge_pools(current_era + 3); + SubPoolsStorage::::insert(1, sub_pools); assert_eq!( - SubPoolsStorage::::get(PRIMARY_ACCOUNT).unwrap(), + SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: UnbondPool { points: 550 + 40 + 10, balance: 550 + 40 + 5 }, with_era: Default::default() @@ -1736,11 +1696,11 @@ mod withdraw_unbonded_other { // Then assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().no_era, + SubPoolsStorage::::get(1).unwrap().no_era, UnbondPool { points: 40 + 10, balance: 40 + 5 + 5 } ); assert_eq!(Balances::free_balance(&550), 550 + 545); - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 50); + assert_eq!(Balances::free_balance(&default_bonded_account()), 50); assert!(!Delegators::::contains_key(550)); // When @@ -1748,11 +1708,11 @@ mod withdraw_unbonded_other { // Then assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().no_era, + SubPoolsStorage::::get(1).unwrap().no_era, UnbondPool { points: 10, balance: 10 } ); assert_eq!(Balances::free_balance(&40), 40 + 40); - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 50 - 40); + assert_eq!(Balances::free_balance(&default_bonded_account()), 50 - 40); assert!(!Delegators::::contains_key(40)); // When @@ -1760,12 +1720,12 @@ mod withdraw_unbonded_other { // Then assert_eq!(Balances::free_balance(&10), 10 + 10); - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 0); + assert_eq!(Balances::free_balance(&default_bonded_account()), 0); assert!(!Delegators::::contains_key(10)); // Pools are removed from storage because the depositor left - assert!(!SubPoolsStorage::::contains_key(&PRIMARY_ACCOUNT),); - assert!(!RewardPools::::contains_key(&PRIMARY_ACCOUNT),); - assert!(!BondedPools::::contains_key(&PRIMARY_ACCOUNT),); + assert!(!SubPoolsStorage::::contains_key(1),); + assert!(!RewardPools::::contains_key(1),); + assert!(!BondedPools::::contains_key(1),); }); } @@ -1777,17 +1737,17 @@ mod withdraw_unbonded_other { .add_delegators(vec![(40, 40), (550, 550)]) .build_and_execute(|| { // Given - StakingMock::set_bonded_balance(PRIMARY_ACCOUNT, 100); // slash bonded balance - Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 100); - assert_eq!(StakingMock::locked_balance(&PRIMARY_ACCOUNT), Some(100)); + StakingMock::set_bonded_balance(default_bonded_account(), 100); // slash bonded balance + Balances::make_free_balance_be(&default_bonded_account(), 100); + assert_eq!(StakingMock::locked_balance(&default_bonded_account()), Some(100)); assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); SubPoolsStorage::::insert( - PRIMARY_ACCOUNT, + 1, SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { 0 + 3 => UnbondPool { points: 600, balance: 100 }}, @@ -1800,11 +1760,11 @@ mod withdraw_unbonded_other { // Then assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, + SubPoolsStorage::::get(&1).unwrap().with_era, sub_pools_with_era! { 0 + 3 => UnbondPool { points: 560, balance: 94 }} ); assert_eq!(Balances::free_balance(&40), 40 + 6); - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 94); + assert_eq!(Balances::free_balance(&default_bonded_account()), 94); assert!(!Delegators::::contains_key(40)); // When @@ -1812,12 +1772,12 @@ mod withdraw_unbonded_other { // Then assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, + SubPoolsStorage::::get(&1).unwrap().with_era, sub_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 2 }} ); assert_eq!(Balances::free_balance(&550), 550 + 92); // The account was dusted because it went below ED(5) - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 0); + assert_eq!(Balances::free_balance(&default_bonded_account()), 0); assert!(!Delegators::::contains_key(550)); // When @@ -1825,12 +1785,12 @@ mod withdraw_unbonded_other { // Then assert_eq!(Balances::free_balance(&10), 10 + 0); - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 0); + assert_eq!(Balances::free_balance(&default_bonded_account()), 0); assert!(!Delegators::::contains_key(10)); // Pools are removed from storage because the depositor left - assert!(!SubPoolsStorage::::contains_key(&PRIMARY_ACCOUNT),); - assert!(!RewardPools::::contains_key(&PRIMARY_ACCOUNT),); - assert!(!BondedPools::::contains_key(&PRIMARY_ACCOUNT),); + assert!(!SubPoolsStorage::::contains_key(1),); + assert!(!RewardPools::::contains_key(1),); + assert!(!BondedPools::::contains_key(1),); }); } @@ -1840,14 +1800,14 @@ mod withdraw_unbonded_other { // Given assert_eq!(Balances::minimum_balance(), 5); assert_eq!(Balances::free_balance(&10), 10); - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 10); - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + assert_eq!(Balances::free_balance(&default_bonded_account()), 10); + unsafe_set_state(1, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); // Simulate a slash that is not accounted for in the sub pools. - Balances::make_free_balance_be(&PRIMARY_ACCOUNT, 5); + Balances::make_free_balance_be(&default_bonded_account(), 5); assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap().with_era, + SubPoolsStorage::::get(1).unwrap().with_era, //------------------------------balance decrease is not account for sub_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 } } ); @@ -1859,7 +1819,7 @@ mod withdraw_unbonded_other { // Then assert_eq!(Balances::free_balance(10), 10 + 5); - assert_eq!(Balances::free_balance(&PRIMARY_ACCOUNT), 0); + assert_eq!(Balances::free_balance(&default_bonded_account()), 0); }); } @@ -1879,7 +1839,7 @@ mod withdraw_unbonded_other { ); let mut delegator = Delegator { - bonded_pool_account: PRIMARY_ACCOUNT, + pool_id: 1, points: 10, reward_pool_total_earnings: 0, unbonding_era: None, @@ -1918,16 +1878,15 @@ mod withdraw_unbonded_other { assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool::::get(1).unwrap(), BondedPool { - points: 10, - state: PoolState::Open, - depositor: 10, - account: PRIMARY_ACCOUNT, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 3, + id: 1, + inner: BondedPoolInner { + points: 10, + state: PoolState::Open, + delegator_counter: 3, + roles: DEFAULT_ROLES + } } ); CurrentEra::set(StakingMock::bonding_duration()); @@ -1939,7 +1898,7 @@ mod withdraw_unbonded_other { ); // Given - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Blocked).unwrap(); + unsafe_set_state(1, PoolState::Blocked).unwrap(); // Cannot kick as a nominator assert_noop!( @@ -1957,10 +1916,7 @@ mod withdraw_unbonded_other { assert_eq!(Balances::free_balance(200), 200 + 200); assert!(!Delegators::::contains_key(100)); assert!(!Delegators::::contains_key(200)); - assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), - Default::default() - ); + assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); }); } @@ -1970,16 +1926,15 @@ mod withdraw_unbonded_other { // Given assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(), + BondedPool::::get(1).unwrap(), BondedPool { - points: 10, - state: PoolState::Open, - depositor: 10, - account: PRIMARY_ACCOUNT, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 2, + id: 1, + inner: BondedPoolInner { + points: 10, + state: PoolState::Open, + delegator_counter: 2, + roles: DEFAULT_ROLES, + } } ); CurrentEra::set(StakingMock::bonding_duration()); @@ -1992,15 +1947,12 @@ mod withdraw_unbonded_other { ); // Given - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying).unwrap(); // Can permissionlesly withdraw a delegator that is not the depositor assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 100, 0)); - assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), - Default::default(), - ); + assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default(),); assert_eq!(Balances::free_balance(100), 100 + 100); assert!(!Delegators::::contains_key(100)); }); @@ -2018,11 +1970,11 @@ mod withdraw_unbonded_other { CurrentEra::set(current_era); assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { @@ -2044,7 +1996,7 @@ mod withdraw_unbonded_other { // Given assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 100, 0)); assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { @@ -2063,7 +2015,7 @@ mod withdraw_unbonded_other { // Given assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 200, 0)); assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: Default::default(), with_era: sub_pools_with_era! { @@ -2077,9 +2029,9 @@ mod withdraw_unbonded_other { assert!(!Delegators::::contains_key(10)); assert_eq!(Balances::free_balance(10), 10 + 10); // Pools are removed from storage because the depositor left - assert!(!SubPoolsStorage::::contains_key(&PRIMARY_ACCOUNT),); - assert!(!RewardPools::::contains_key(&PRIMARY_ACCOUNT),); - assert!(!BondedPools::::contains_key(&PRIMARY_ACCOUNT),); + assert!(!SubPoolsStorage::::contains_key(1)); + assert!(!RewardPools::::contains_key(1)); + assert!(!BondedPools::::contains_key(1)); }); } @@ -2088,7 +2040,7 @@ mod withdraw_unbonded_other { ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); // Skip ahead to an era where the `with_era` pools can get merged into the `no_era` // pool. @@ -2096,12 +2048,11 @@ mod withdraw_unbonded_other { CurrentEra::set(current_era); // Simulate some other withdraw that caused the pool to merge - let sub_pools = SubPoolsStorage::::get(&PRIMARY_ACCOUNT) - .unwrap() - .maybe_merge_pools(current_era + 3); - SubPoolsStorage::::insert(&PRIMARY_ACCOUNT, sub_pools); + let sub_pools = + SubPoolsStorage::::get(1).unwrap().maybe_merge_pools(current_era + 3); + SubPoolsStorage::::insert(1, sub_pools); assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: UnbondPool { points: 100 + 10, balance: 100 + 10 }, with_era: Default::default(), @@ -2117,7 +2068,7 @@ mod withdraw_unbonded_other { // Given assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 100, 0)); assert_eq!( - SubPoolsStorage::::get(&PRIMARY_ACCOUNT).unwrap(), + SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: UnbondPool { points: 10, balance: 10 }, with_era: Default::default(), @@ -2129,9 +2080,9 @@ mod withdraw_unbonded_other { assert!(!Delegators::::contains_key(10)); assert_eq!(Balances::free_balance(10), 10 + 10); // Pools are removed from storage because the depositor left - assert!(!SubPoolsStorage::::contains_key(&PRIMARY_ACCOUNT)); - assert!(!RewardPools::::contains_key(&PRIMARY_ACCOUNT)); - assert!(!BondedPools::::contains_key(&PRIMARY_ACCOUNT)); + assert!(!SubPoolsStorage::::contains_key(1)); + assert!(!RewardPools::::contains_key(1)); + assert!(!BondedPools::::contains_key(1)); }); } } @@ -2142,12 +2093,13 @@ mod create { #[test] fn create_works() { ExtBuilder::default().build_and_execute(|| { - let stash = 3548237456; + // next pool id is 2. + let next_pool_stash = Pools::create_bonded_account(2); - assert!(!BondedPools::::contains_key(1)); - assert!(!RewardPools::::contains_key(1)); + assert!(!BondedPools::::contains_key(2)); + assert!(!RewardPools::::contains_key(2)); assert!(!Delegators::::contains_key(11)); - assert_eq!(StakingMock::bonded_balance(&stash), None); + assert_eq!(StakingMock::bonded_balance(&next_pool_stash), None); Balances::make_free_balance_be(&11, StakingMock::minimum_bond()); assert_ok!(Pools::create( @@ -2162,33 +2114,39 @@ mod create { assert_eq!( Delegators::::get(11).unwrap(), Delegator { - bonded_pool_account: stash, + pool_id: 2, points: StakingMock::minimum_bond(), reward_pool_total_earnings: Zero::zero(), unbonding_era: None } ); assert_eq!( - BondedPool::::get(&stash).unwrap(), + BondedPool::::get(2).unwrap(), BondedPool { - points: StakingMock::minimum_bond(), - depositor: 11, - state: PoolState::Open, - account: stash.clone(), - root: 123, - nominator: 456, - state_toggler: 789, - delegator_counter: 1, + id: 2, + inner: BondedPoolInner { + points: StakingMock::minimum_bond(), + delegator_counter: 1, + state: PoolState::Open, + roles: PoolRoles { + depositor: 11, + root: 123, + nominator: 456, + state_toggler: 789 + } + } } ); - assert_eq!(StakingMock::bonded_balance(&stash).unwrap(), StakingMock::minimum_bond()); assert_eq!( - RewardPools::::get(stash).unwrap(), + StakingMock::bonded_balance(&next_pool_stash).unwrap(), + StakingMock::minimum_bond() + ); + assert_eq!( + RewardPools::::get(2).unwrap(), RewardPool { balance: Zero::zero(), points: U256::zero(), total_earnings: Zero::zero(), - account: 1657614948 } ); }); @@ -2197,10 +2155,6 @@ mod create { #[test] fn create_errors_correctly() { ExtBuilder::default().build_and_execute_no_checks(|| { - // assert_noop!( - // Pools::create(Origin::signed(10), 420, 123, 456, 789), - // Error::::AccountBelongsToOtherPool - // ); assert_noop!( Pools::create(Origin::signed(10), 420, 123, 456, 789), Error::::AccountBelongsToOtherPool @@ -2227,14 +2181,13 @@ mod create { // Given BondedPool:: { - depositor: 10, - state: PoolState::Open, - points: 10, - account: 123, - root: 900, - nominator: 901, - state_toggler: 902, - delegator_counter: 1, + id: 2, + inner: BondedPoolInner { + state: PoolState::Open, + points: 10, + delegator_counter: 1, + roles: DEFAULT_ROLES, + }, } .put(); assert_eq!(MaxPools::::get(), Some(2)); @@ -2268,22 +2221,22 @@ mod nominate { ExtBuilder::default().build_and_execute(|| { // Depositor can't nominate assert_noop!( - Pools::nominate(Origin::signed(10), PRIMARY_ACCOUNT, vec![21]), + Pools::nominate(Origin::signed(10), 1, vec![21]), Error::::NotNominator ); // State toggler can't nominate assert_noop!( - Pools::nominate(Origin::signed(902), PRIMARY_ACCOUNT, vec![21]), + Pools::nominate(Origin::signed(902), 1, vec![21]), Error::::NotNominator ); // Root can nominate - assert_ok!(Pools::nominate(Origin::signed(900), PRIMARY_ACCOUNT, vec![21])); + assert_ok!(Pools::nominate(Origin::signed(900), 1, vec![21])); assert_eq!(Nominations::get(), vec![21]); // Nominator can nominate - assert_ok!(Pools::nominate(Origin::signed(901), PRIMARY_ACCOUNT, vec![31])); + assert_ok!(Pools::nominate(Origin::signed(901), 1, vec![31])); assert_eq!(Nominations::get(), vec![31]); // Can't nominate for a pool that doesn't exist @@ -2302,89 +2255,61 @@ mod set_state_other { fn set_state_other_works() { ExtBuilder::default().build_and_execute(|| { // Given - assert_ok!(BondedPool::::get(&PRIMARY_ACCOUNT).unwrap().ok_to_be_open()); + assert_ok!(BondedPool::::get(1).unwrap().ok_to_be_open()); // Only the root and state toggler can change the state when the pool is ok to be open. assert_noop!( - Pools::set_state_other(Origin::signed(10), PRIMARY_ACCOUNT, PoolState::Blocked), + Pools::set_state_other(Origin::signed(10), 1, PoolState::Blocked), Error::::CanNotChangeState ); assert_noop!( - Pools::set_state_other(Origin::signed(901), PRIMARY_ACCOUNT, PoolState::Blocked), + Pools::set_state_other(Origin::signed(901), 1, PoolState::Blocked), Error::::CanNotChangeState ); // Root can change state - assert_ok!(Pools::set_state_other( - Origin::signed(900), - PRIMARY_ACCOUNT, - PoolState::Blocked - )); - assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap().state, - PoolState::Blocked - ); + assert_ok!(Pools::set_state_other(Origin::signed(900), 1, PoolState::Blocked)); + assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Blocked); // State toggler can change state - assert_ok!(Pools::set_state_other( - Origin::signed(902), - PRIMARY_ACCOUNT, - PoolState::Destroying - )); - assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap().state, - PoolState::Destroying - ); + assert_ok!(Pools::set_state_other(Origin::signed(902), 1, PoolState::Destroying)); + assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Destroying); // If the pool is destroying, then no one can set state assert_noop!( - Pools::set_state_other(Origin::signed(900), PRIMARY_ACCOUNT, PoolState::Blocked), + Pools::set_state_other(Origin::signed(900), 1, PoolState::Blocked), Error::::CanNotChangeState ); assert_noop!( - Pools::set_state_other(Origin::signed(902), PRIMARY_ACCOUNT, PoolState::Blocked), + Pools::set_state_other(Origin::signed(902), 1, PoolState::Blocked), Error::::CanNotChangeState ); // If the pool is not ok to be open, then anyone can set it to destroying // Given - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Open).unwrap(); - let mut bonded_pool = BondedPool::::get(&PRIMARY_ACCOUNT).unwrap(); + unsafe_set_state(1, PoolState::Open).unwrap(); + let mut bonded_pool = BondedPool::::get(1).unwrap(); bonded_pool.points = 100; bonded_pool.put(); // When - assert_ok!(Pools::set_state_other( - Origin::signed(11), - PRIMARY_ACCOUNT, - PoolState::Destroying - )); + assert_ok!(Pools::set_state_other(Origin::signed(11), 1, PoolState::Destroying)); // Then - assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap().state, - PoolState::Destroying - ); + assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Destroying); // Given - Balances::make_free_balance_be(&PRIMARY_ACCOUNT, Balance::max_value() / 10); - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Open).unwrap(); + Balances::make_free_balance_be(&default_bonded_account(), Balance::max_value() / 10); + unsafe_set_state(1, PoolState::Open).unwrap(); // When - assert_ok!(Pools::set_state_other( - Origin::signed(11), - PRIMARY_ACCOUNT, - PoolState::Destroying - )); + assert_ok!(Pools::set_state_other(Origin::signed(11), 1, PoolState::Destroying)); // Then - assert_eq!( - BondedPool::::get(&PRIMARY_ACCOUNT).unwrap().state, - PoolState::Destroying - ); + assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Destroying); // If the pool is not ok to be open, it cannot be permissionleslly set to a state that // isn't destroying - unsafe_set_state(&PRIMARY_ACCOUNT, PoolState::Open).unwrap(); + unsafe_set_state(1, PoolState::Open).unwrap(); assert_noop!( - Pools::set_state_other(Origin::signed(11), PRIMARY_ACCOUNT, PoolState::Blocked), + Pools::set_state_other(Origin::signed(11), 1, PoolState::Blocked), Error::::CanNotChangeState ); }); @@ -2398,28 +2323,28 @@ mod set_metadata { fn set_metadata_works() { ExtBuilder::default().build_and_execute(|| { // Root can set metadata - assert_ok!(Pools::set_metadata(Origin::signed(900), PRIMARY_ACCOUNT, vec![1, 1])); - assert_eq!(Metadata::::get(PRIMARY_ACCOUNT), vec![1, 1]); + assert_ok!(Pools::set_metadata(Origin::signed(900), 1, vec![1, 1])); + assert_eq!(Metadata::::get(1), vec![1, 1]); // State toggler can set metadata - assert_ok!(Pools::set_metadata(Origin::signed(902), PRIMARY_ACCOUNT, vec![2, 2])); - assert_eq!(Metadata::::get(PRIMARY_ACCOUNT), vec![2, 2]); + assert_ok!(Pools::set_metadata(Origin::signed(902), 1, vec![2, 2])); + assert_eq!(Metadata::::get(1), vec![2, 2]); // Depositor can't set metadata assert_noop!( - Pools::set_metadata(Origin::signed(10), PRIMARY_ACCOUNT, vec![3, 3]), + Pools::set_metadata(Origin::signed(10), 1, vec![3, 3]), Error::::DoesNotHavePermission ); // Nominator can't set metadata assert_noop!( - Pools::set_metadata(Origin::signed(901), PRIMARY_ACCOUNT, vec![3, 3]), + Pools::set_metadata(Origin::signed(901), 1, vec![3, 3]), Error::::DoesNotHavePermission ); // Metadata cannot be longer than `MaxMetadataLen` assert_noop!( - Pools::set_metadata(Origin::signed(900), PRIMARY_ACCOUNT, vec![1, 1, 1]), + Pools::set_metadata(Origin::signed(900), 1, vec![1, 1, 1]), Error::::MetadataExceedsMaxLen ); }); From abda7649a9d4f8d123dc3956ac3f5ec13e1d377b Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Tue, 15 Mar 2022 11:55:08 +0000 Subject: [PATCH 182/299] nomination-pools: update benches for new account format (#11033) * Update benches to new account format * More sensible seeds * bring back rward account sanity check * Comment --- .../nomination-pools/benchmarking/src/lib.rs | 81 +++++++++---------- .../nomination-pools/benchmarking/src/mock.rs | 2 +- frame/nomination-pools/src/lib.rs | 10 +-- 3 files changed, 46 insertions(+), 47 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index cc3f6b314d8c6..e8c6ca85267d2 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -68,7 +68,7 @@ fn create_pool_account( let pool_account = pallet_nomination_pools::BondedPools::::iter() .find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator) - .map(|(pool_account, _)| pool_account) + .map(|(pool_id, _)| Pools::::create_bonded_account(pool_id)) .expect("pool_creator created a pool above"); (pool_creator, pool_account) @@ -117,15 +117,15 @@ impl ListScenario { let i = CurrencyOf::::burn(CurrencyOf::::total_issuance()); sp_std::mem::forget(i); - // create accounts with the origin weight - let (_, pool_origin1) = create_pool_account::(USER_SEED + 2, origin_weight); + // Create accounts with the origin weight + let (_, pool_origin1) = create_pool_account::(USER_SEED + 1, origin_weight); T::StakingInterface::nominate( pool_origin1.clone(), // NOTE: these don't really need to be validators. vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], )?; - let (_, pool_origin2) = create_pool_account::(USER_SEED + 3, origin_weight); + let (_, pool_origin2) = create_pool_account::(USER_SEED + 2, origin_weight); T::StakingInterface::nominate( pool_origin2.clone(), vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))].clone(), @@ -142,7 +142,7 @@ impl ListScenario { dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; // Create an account with the worst case destination weight - let (_, pool_dest1) = create_pool_account::(USER_SEED + 1, dest_weight); + let (_, pool_dest1) = create_pool_account::(USER_SEED + 3, dest_weight); T::StakingInterface::nominate( pool_dest1.clone(), vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], @@ -175,12 +175,11 @@ impl ListScenario { .expect("the pool was created in `Self::new`."); // Account pool points for the unbonded balance. - BondedPools::::mutate(&self.origin1, |maybe_pool| { + BondedPools::::mutate(&1, |maybe_pool| { maybe_pool.as_mut().map(|pool| pool.points -= amount) }); - Pools::::join(Origin::Signed(joiner.clone()).into(), amount, self.origin1.clone()) - .unwrap(); + Pools::::join(Origin::Signed(joiner.clone()).into(), amount, 1).unwrap(); // Sanity check that the vote weight is still the same as the original bonded let weight_of = pallet_staking::Pallet::::weight_of_fn(); @@ -189,7 +188,7 @@ impl ListScenario { // Sanity check the delegator was added correctly let delegator = Delegators::::get(&joiner).unwrap(); assert_eq!(delegator.points, amount); - assert_eq!(delegator.bonded_pool_account, self.origin1); + assert_eq!(delegator.pool_id, 1); self } @@ -217,7 +216,7 @@ frame_benchmarking::benchmarks! { = create_funded_user_with_balance::("joiner", 0, joiner_free); whitelist_account!(joiner); - }: _(Origin::Signed(joiner.clone()), max_additional, scenario.origin1.clone()) + }: _(Origin::Signed(joiner.clone()), max_additional, 1) verify { assert_eq!(CurrencyOf::::free_balance(&joiner), joiner_free - max_additional); assert_eq!( @@ -232,9 +231,7 @@ frame_benchmarking::benchmarks! { let origin_weight = pallet_nomination_pools::MinCreateBond::::get().max(CurrencyOf::::minimum_balance()) * 2u32.into(); let (depositor, pool_account) = create_pool_account::(0, origin_weight); - let reward_account = pallet_nomination_pools::RewardPools::::get(pool_account) - .map(|r| r.account.clone()) - .expect("pool created above."); + let reward_account = Pools::::create_reward_account(1); // Send funds to the reward account of the pool CurrencyOf::::make_free_balance_be(&reward_account, origin_weight); @@ -297,7 +294,7 @@ frame_benchmarking::benchmarks! { // Add a new delegator let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); - Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) + Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, 1) .unwrap(); // Sanity check join worked @@ -322,7 +319,7 @@ frame_benchmarking::benchmarks! { // Add `s` count of slashing spans to storage. pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); whitelist_account!(pool_account); - }: _(Origin::Signed(pool_account.clone()), pool_account.clone(), s) + }: _(Origin::Signed(pool_account.clone()), 1, s) verify { // The joiners funds didn't change assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); @@ -342,7 +339,7 @@ frame_benchmarking::benchmarks! { // Add a new delegator let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); - Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, pool_account.clone()) + Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, 1) .unwrap(); // Sanity check join worked @@ -389,7 +386,7 @@ frame_benchmarking::benchmarks! { let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // We set the pool to the destroying state so the depositor can leave - BondedPools::::try_mutate(&pool_account, |maybe_bonded_pool| { + BondedPools::::try_mutate(&1, |maybe_bonded_pool| { maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { bonded_pool.state = PoolState::Destroying; }) @@ -398,7 +395,15 @@ frame_benchmarking::benchmarks! { // Unbond the creator pallet_staking::CurrentEra::::put(0); + // Simulate some rewards so we can check if the rewards storage is cleaned up. We check this + // here to ensure the complete flow for destroying a pool works - the reward pool account + // should never exist by time the depositor withdraws so we test that it gets cleaned + // up when unbonding. + let reward_account = Pools::::create_reward_account(1); + CurrencyOf::::make_free_balance_be(&reward_account, CurrencyOf::::minimum_balance()); + assert!(frame_system::Account::::contains_key(&reward_account)); Pools::::unbond_other(Origin::Signed(depositor.clone()).into(), depositor.clone()).unwrap(); + assert!(!frame_system::Account::::contains_key(&reward_account)); // Sanity check that unbond worked assert_eq!( @@ -416,33 +421,27 @@ frame_benchmarking::benchmarks! { // Some last checks that storage items we expect to get cleaned up are present assert!(pallet_staking::Ledger::::contains_key(&pool_account)); - assert!(BondedPools::::contains_key(&pool_account)); - assert!(SubPoolsStorage::::contains_key(&pool_account)); - assert!(RewardPools::::contains_key(&pool_account)); + assert!(BondedPools::::contains_key(&1)); + assert!(SubPoolsStorage::::contains_key(&1)); + assert!(RewardPools::::contains_key(&1)); assert!(Delegators::::contains_key(&depositor)); - let reward_account = pallet_nomination_pools::RewardPools::::get(&pool_account) - .map(|r| r.account.clone()) - .expect("pool created above."); - // Simulate some rewards so we can check if the rewards storage is cleaned up - CurrencyOf::::make_free_balance_be(&reward_account, CurrencyOf::::minimum_balance()); - assert!(frame_system::Account::::contains_key(&reward_account)); whitelist_account!(depositor); }: withdraw_unbonded_other(Origin::Signed(depositor.clone()), depositor.clone(), s) verify { // Pool removal worked assert!(!pallet_staking::Ledger::::contains_key(&pool_account)); - assert!(!BondedPools::::contains_key(&pool_account)); - assert!(!SubPoolsStorage::::contains_key(&pool_account)); - assert!(!RewardPools::::contains_key(&pool_account)); + assert!(!BondedPools::::contains_key(&1)); + assert!(!SubPoolsStorage::::contains_key(&1)); + assert!(!RewardPools::::contains_key(&1)); assert!(!Delegators::::contains_key(&depositor)); assert!(!frame_system::Account::::contains_key(&pool_account)); - assert!(!frame_system::Account::::contains_key(&reward_account)); // Funds where transferred back correctly assert_eq!( CurrencyOf::::free_balance(&depositor), - min_create_bond * 2u32.into() + // gets bond back + rewards collecting when unbonding + min_create_bond * 2u32.into() + CurrencyOf::::minimum_balance() ); } @@ -472,7 +471,7 @@ frame_benchmarking::benchmarks! { verify { assert_eq!(RewardPools::::count(), 1); assert_eq!(BondedPools::::count(), 1); - let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); + let (_, new_pool) = BondedPools::::iter().next().unwrap(); assert_eq!( new_pool, BondedPoolInner { @@ -488,7 +487,7 @@ frame_benchmarking::benchmarks! { } ); assert_eq!( - T::StakingInterface::bonded_balance(&pool_account), + T::StakingInterface::bonded_balance(&Pools::::create_bonded_account(1)), Some(min_create_bond) ); } @@ -511,11 +510,11 @@ frame_benchmarking::benchmarks! { .collect(); whitelist_account!(depositor); - }:_(Origin::Signed(depositor.clone()), pool_account, validators) + }:_(Origin::Signed(depositor.clone()), 1, validators) verify { assert_eq!(RewardPools::::count(), 1); assert_eq!(BondedPools::::count(), 1); - let (pool_account, new_pool) = BondedPools::::iter().next().unwrap(); + let (_, new_pool) = BondedPools::::iter().next().unwrap(); assert_eq!( new_pool, BondedPoolInner { @@ -531,7 +530,7 @@ frame_benchmarking::benchmarks! { } ); assert_eq!( - T::StakingInterface::bonded_balance(&pool_account), + T::StakingInterface::bonded_balance(&Pools::::create_bonded_account(1)), Some(min_create_bond) ); } @@ -542,16 +541,16 @@ frame_benchmarking::benchmarks! { .max(T::StakingInterface::minimum_bond()) .max(CurrencyOf::::minimum_balance()); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); - BondedPools::::mutate(&pool_account, |maybe_pool| { + BondedPools::::mutate(&1, |maybe_pool| { // Force the pool into an invalid state maybe_pool.as_mut().map(|mut pool| pool.points = min_create_bond * 10u32.into()); }); let caller = account("caller", 0, USER_SEED); whitelist_account!(caller); - }:_(Origin::Signed(caller), pool_account.clone(), PoolState::Destroying) + }:_(Origin::Signed(caller), 1, PoolState::Destroying) verify { - assert_eq!(BondedPools::::get(pool_account).unwrap().state, PoolState::Destroying); + assert_eq!(BondedPools::::get(1).unwrap().state, PoolState::Destroying); } set_metadata { @@ -567,9 +566,9 @@ frame_benchmarking::benchmarks! { let metadata: Vec = (0..::MaxMetadataLen::get()).map(|_| 42).collect(); whitelist_account!(depositor); - }:_(Origin::Signed(depositor), pool_account.clone(), metadata.clone()) + }:_(Origin::Signed(depositor), 1, metadata.clone()) verify { - assert_eq!(Metadata::::get(&pool_account), metadata); + assert_eq!(Metadata::::get(&1), metadata); } } diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 895b9b672c96d..6dfc93f1a790b 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -19,7 +19,7 @@ use frame_election_provider_support::VoteWeight; use frame_support::{pallet_prelude::*, parameter_types, traits::ConstU64, PalletId}; use sp_runtime::traits::{Convert, IdentityLookup}; -type AccountId = u64; +type AccountId = u128; type AccountIndex = u32; type BlockNumber = u64; type Balance = u128; diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 2ac769b50a506..0f1127ed71178 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -621,8 +621,7 @@ impl BondedPool { // This is a depositor if !sub_pools.no_era.points.is_zero() { // Unbonded pool has some points, so if they are the last delegator they must be - // here - // Since the depositor is the last to unbond, this should never be possible + // here. Since the depositor is the last to unbond, this should never be possible. ensure!(sub_pools.with_era.len().is_zero(), Error::::NotOnlyDelegator); ensure!( sub_pools.no_era.points == target_delegator.points, @@ -1539,14 +1538,15 @@ pub mod pallet { impl Pallet { /// Create the main, bonded account of a pool with the given id. - fn create_bonded_account(id: PoolId) -> T::AccountId { + pub fn create_bonded_account(id: PoolId) -> T::AccountId { + // 4bytes ++ 8bytes ++ 1byte ++ 4bytes T::PalletId::get().into_sub_account((1u8, id)) } /// Create the reward account of a pool with the given id. - fn create_reward_account(id: PoolId) -> T::AccountId { + pub fn create_reward_account(id: PoolId) -> T::AccountId { // TODO: integrity check for what is the reasonable max number of pools based on this. - // 4 + 8 + 4 + 1 + // 4bytes ++ 8bytes ++ 1byte ++ 4bytes T::PalletId::get().into_sub_account((2u8, id)) } From f113a6c2e4be4e8bbbd8027550cf7ed31c61ae84 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Tue, 15 Mar 2022 14:36:26 +0000 Subject: [PATCH 183/299] Add extrinsic set_configs (#11038) --- .../nomination-pools/benchmarking/src/lib.rs | 23 ++++++- frame/nomination-pools/src/lib.rs | 60 ++++++++++++++++--- frame/nomination-pools/src/sanity.rs | 2 +- frame/nomination-pools/src/tests.rs | 51 +++++++++++++++- frame/nomination-pools/src/weights.rs | 13 +++- 5 files changed, 133 insertions(+), 16 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index e8c6ca85267d2..1452b8250f116 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -11,10 +11,11 @@ use frame_election_provider_support::SortedListProvider; use frame_support::{ensure, traits::Get}; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ - BalanceOf, BondedPoolInner, BondedPools, Delegators, Metadata, MinCreateBond, MinJoinBond, - Pallet as Pools, PoolRoles, PoolState, RewardPools, SubPoolsStorage, + BalanceOf, BondedPoolInner, BondedPools, ConfigOp, Delegators, MaxDelegators, + MaxDelegatorsPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, Pallet as Pools, + PoolRoles, PoolState, RewardPools, SubPoolsStorage, }; -use sp_runtime::traits::{StaticLookup, Zero}; +use sp_runtime::traits::{Bounded, StaticLookup, Zero}; use sp_staking::{EraIndex, StakingInterface}; // `frame_benchmarking::benchmarks!` macro needs this use pallet_nomination_pools::Call; @@ -570,6 +571,22 @@ frame_benchmarking::benchmarks! { verify { assert_eq!(Metadata::::get(&1), metadata); } + + set_configs { + }:_( + Origin::Root, + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(u32::MAX) + ) verify { + assert_eq!(MinJoinBond::::get(), BalanceOf::::max_value()); + assert_eq!(MinCreateBond::::get(), BalanceOf::::max_value()); + assert_eq!(MaxPools::::get(), Some(u32::MAX)); + assert_eq!(MaxDelegators::::get(), Some(u32::MAX)); + assert_eq!(MaxDelegatorsPerPool::::get(), Some(u32::MAX)); + } } // TODO: consider benchmarking slashing logic with pools diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 0f1127ed71178..a17407f0a64a3 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -299,6 +299,7 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] +use codec::Codec; use frame_support::{ ensure, pallet_prelude::{MaxEncodedLen, *}, @@ -313,7 +314,7 @@ use scale_info::TypeInfo; use sp_core::U256; use sp_runtime::traits::{AccountIdConversion, Bounded, Convert, Saturating, StaticLookup, Zero}; use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; -use sp_std::{collections::btree_map::BTreeMap, ops::Div, vec::Vec}; +use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, ops::Div, vec::Vec}; #[cfg(test)] mod mock; @@ -336,6 +337,17 @@ pub type PoolId = u32; const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; +/// Possible operations on the configuration values of this pallet. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq, Clone)] +pub enum ConfigOp { + /// Don't change. + Noop, + /// Set the given value. + Set(T), + /// Remove from storage. + Remove, +} + /// Extrinsics that bond some funds to the pool. enum PoolBond { Create, @@ -1512,15 +1524,45 @@ pub mod pallet { Ok(()) } - // Set - // * `min_join_bond` - // * `min_create_bond` - // * `max_pools` - // * `max_delegators_per_pool` - // * `max_delegators` - // pub fn set_parameters(origin: OriginFor, ) -> DispatchResult { + /// Update configurations for the nomination pools. The origin must for this call must be + /// Root. + /// + /// # Arguments + /// + /// * `min_join_bond` - Set [`Self::MinJoinBond`]. + /// * `min_create_bond` - Set [`Self::MinCreateBond`]. + /// * `max_pools` - Set [`Self::MaxPools`]. + /// * `max_delegators` - Set [`Self::MaxDelegators`]. + /// * `max_delegators_per_pool` - Set [`Self::MaxDelegatorsPerPool`]. + #[pallet::weight(T::WeightInfo::set_configs())] + pub fn set_configs( + origin: OriginFor, + min_join_bond: ConfigOp>, + min_create_bond: ConfigOp>, + max_pools: ConfigOp, + max_delegators: ConfigOp, + max_delegators_per_pool: ConfigOp, + ) -> DispatchResult { + ensure_root(origin)?; + + macro_rules! config_op_exp { + ($storage:ty, $op:ident) => { + match $op { + ConfigOp::Noop => (), + ConfigOp::Set(v) => <$storage>::put(v), + ConfigOp::Remove => <$storage>::kill(), + } + }; + } + + config_op_exp!(MinJoinBond::, min_join_bond); + config_op_exp!(MinCreateBond::, min_create_bond); + config_op_exp!(MaxPools::, max_pools); + config_op_exp!(MaxDelegators::, max_delegators); + config_op_exp!(MaxDelegatorsPerPool::, max_delegators_per_pool); - // } + Ok(()) + } } #[pallet::hooks] diff --git a/frame/nomination-pools/src/sanity.rs b/frame/nomination-pools/src/sanity.rs index 389065452ed11..1a40110d8ee94 100644 --- a/frame/nomination-pools/src/sanity.rs +++ b/frame/nomination-pools/src/sanity.rs @@ -12,7 +12,7 @@ fn bonding_pools_checks() { let mut delegators_seen = 0; for (_account, pool) in BondedPools::::iter() { assert!(pool.delegator_counter >= 1); - assert!(pool.delegator_counter <= MaxDelegatorsPerPool::::get().unwrap()); + assert!(MaxDelegatorsPerPool::::get().map_or(true, |max| pool.delegator_counter <= max)); delegators_seen += pool.delegator_counter; } assert_eq!(delegators_seen, Delegators::::count()); diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 51c1d33e3a1ca..0a024a298623c 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1,6 +1,6 @@ use super::*; use crate::mock::*; -use frame_support::{assert_noop, assert_ok}; +use frame_support::{assert_noop, assert_ok, assert_storage_noop}; macro_rules! sub_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ @@ -2350,3 +2350,52 @@ mod set_metadata { }); } } + +mod set_configs { + use super::*; + + #[test] + fn set_configs_works() { + ExtBuilder::default().build_and_execute(|| { + // Setting works + assert_ok!(Pools::set_configs( + Origin::root(), + ConfigOp::Set(1 as Balance), + ConfigOp::Set(2 as Balance), + ConfigOp::Set(3u32), + ConfigOp::Set(4u32), + ConfigOp::Set(5u32), + )); + assert_eq!(MinJoinBond::::get(), 1); + assert_eq!(MinCreateBond::::get(), 2); + assert_eq!(MaxPools::::get(), Some(3)); + assert_eq!(MaxDelegators::::get(), Some(4)); + assert_eq!(MaxDelegatorsPerPool::::get(), Some(5)); + + // Noop does nothing + assert_storage_noop!(assert_ok!(Pools::set_configs( + Origin::root(), + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ))); + + // Removing works + assert_ok!(Pools::set_configs( + Origin::root(), + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove + )); + assert_eq!(MinJoinBond::::get(), 0); + assert_eq!(MinCreateBond::::get(), 0); + assert_eq!(MaxPools::::get(), None); + assert_eq!(MaxDelegators::::get(), None); + assert_eq!(MaxDelegatorsPerPool::::get(), None); + }); + } +} diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index 373bc28b5d356..4468b39caacf9 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -53,6 +53,7 @@ pub trait WeightInfo { fn nominate() -> Weight; fn set_state_other() -> Weight; fn set_metadata() -> Weight; + fn set_configs() -> Weight; } /// Weights for pallet_nomination_pools using the Substrate node and recommended hardware. @@ -174,7 +175,6 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(28 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } - fn set_state_other() -> Weight { (79_587_000 as Weight) .saturating_add(T::DbWeight::get().reads(28 as Weight)) @@ -185,7 +185,11 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(28 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } - + fn set_configs() -> Weight { + (79_587_000 as Weight) + .saturating_add(T::DbWeight::get().reads(28 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } } // For backwards compatibility and tests @@ -317,4 +321,9 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(28 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } + fn set_configs() -> Weight { + (79_587_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(28 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } } From 15fb06677a4fa16982e4365aa5c694e558dbf455 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 15 Mar 2022 17:46:30 +0000 Subject: [PATCH 184/299] Better sanity checks for nomination pools (#11042) * new sanity checks, few other changes * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov --- .../nomination-pools/benchmarking/src/lib.rs | 22 +- frame/nomination-pools/src/lib.rs | 218 +++++++++++++----- frame/nomination-pools/src/mock.rs | 21 +- frame/nomination-pools/src/tests.rs | 34 +-- frame/staking/src/pallet/impls.rs | 8 +- primitives/staking/src/lib.rs | 15 +- 6 files changed, 203 insertions(+), 115 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 1452b8250f116..6f1defe043dff 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -168,7 +168,7 @@ impl ListScenario { self.origin1_delegator = Some(joiner.clone()); CurrencyOf::::make_free_balance_be(&joiner, amount * 2u32.into()); - let original_bonded = T::StakingInterface::bonded_balance(&self.origin1).unwrap(); + let original_bonded = T::StakingInterface::active_stake(&self.origin1).unwrap(); // Unbond `amount` from the underlying pool account so when the delegator joins // we will maintain `current_bonded`. @@ -206,7 +206,7 @@ frame_benchmarking::benchmarks! { // setup the worst case list scenario. let scenario = ListScenario::::new(origin_weight, true)?; assert_eq!( - T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), + T::StakingInterface::active_stake(&scenario.origin1).unwrap(), origin_weight ); @@ -221,7 +221,7 @@ frame_benchmarking::benchmarks! { verify { assert_eq!(CurrencyOf::::free_balance(&joiner), joiner_free - max_additional); assert_eq!( - T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(), + T::StakingInterface::active_stake(&scenario.origin1).unwrap(), scenario.dest_weight ); } @@ -273,7 +273,7 @@ frame_benchmarking::benchmarks! { whitelist_account!(delegator_id); }: _(Origin::Signed(delegator_id.clone()), delegator_id.clone()) verify { - let bonded_after = T::StakingInterface::bonded_balance(&scenario.origin1).unwrap(); + let bonded_after = T::StakingInterface::active_stake(&scenario.origin1).unwrap(); // We at least went down to the destination bag, (if not an even lower bag) assert!(bonded_after <= scenario.dest_weight.clone()); let delegator = Delegators::::get( @@ -300,7 +300,7 @@ frame_benchmarking::benchmarks! { // Sanity check join worked assert_eq!( - T::StakingInterface::bonded_balance(&pool_account).unwrap(), + T::StakingInterface::active_stake(&pool_account).unwrap(), min_create_bond + min_join_bond ); assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); @@ -310,7 +310,7 @@ frame_benchmarking::benchmarks! { // Sanity check that unbond worked assert_eq!( - T::StakingInterface::bonded_balance(&pool_account).unwrap(), + T::StakingInterface::active_stake(&pool_account).unwrap(), min_create_bond ); assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); @@ -345,7 +345,7 @@ frame_benchmarking::benchmarks! { // Sanity check join worked assert_eq!( - T::StakingInterface::bonded_balance(&pool_account).unwrap(), + T::StakingInterface::active_stake(&pool_account).unwrap(), min_create_bond + min_join_bond ); assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); @@ -356,7 +356,7 @@ frame_benchmarking::benchmarks! { // Sanity check that unbond worked assert_eq!( - T::StakingInterface::bonded_balance(&pool_account).unwrap(), + T::StakingInterface::active_stake(&pool_account).unwrap(), min_create_bond ); assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); @@ -408,7 +408,7 @@ frame_benchmarking::benchmarks! { // Sanity check that unbond worked assert_eq!( - T::StakingInterface::bonded_balance(&pool_account).unwrap(), + T::StakingInterface::active_stake(&pool_account).unwrap(), Zero::zero() ); assert_eq!( @@ -488,7 +488,7 @@ frame_benchmarking::benchmarks! { } ); assert_eq!( - T::StakingInterface::bonded_balance(&Pools::::create_bonded_account(1)), + T::StakingInterface::active_balance(&Pools::::create_bonded_account(1)), Some(min_create_bond) ); } @@ -531,7 +531,7 @@ frame_benchmarking::benchmarks! { } ); assert_eq!( - T::StakingInterface::bonded_balance(&Pools::::create_bonded_account(1)), + T::StakingInterface::active_stake(&Pools::::create_bonded_account(1)), Some(min_create_bond) ); } diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index a17407f0a64a3..ba4bb8692eea5 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -284,19 +284,6 @@ //! * Watch out for overflow of [`RewardPoints`] and [`BalanceOf`] types. Consider things like the //! chains total issuance, staking reward rate, and burn rate. -// Invariants -// * A `delegator.bonded_pool_account` must always be a valid entry in `RewardPools`, and -// `BondedPoolInner`. -// * Every entry in `BondedPoolInner` must have a corresponding entry in `RewardPools` -// * If a delegator unbonds, the sub pools should always correctly track slashses such that the -// calculated amount when withdrawing unbonded is a lower bound of the pools free balance. -// * If the depositor is actively unbonding, the pool is in destroying state. To achieve this, once -// a pool is flipped to a destroying state it cannot change its state. -// * The sum of each pools delegator counter equals the `Delegators::count()`. -// * A pool's `delegator_counter` should always be gt 0. -// * TODO: metadata should only exist if the pool exist. - -// Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] use codec::Codec; @@ -312,15 +299,13 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_core::U256; -use sp_runtime::traits::{AccountIdConversion, Bounded, Convert, Saturating, StaticLookup, Zero}; +use sp_runtime::traits::{AccountIdConversion, Bounded, Convert, Saturating, Zero}; use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, ops::Div, vec::Vec}; #[cfg(test)] mod mock; #[cfg(test)] -mod sanity; -#[cfg(test)] mod tests; pub mod weights; @@ -331,7 +316,6 @@ pub use weights::WeightInfo; pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; pub type SubPoolsWithEra = BoundedBTreeMap, TotalUnbondingPools>; -// NOTE: this assumes the balance type u128 or smaller. TODO: integrity-check pub type RewardPoints = U256; pub type PoolId = u32; @@ -354,6 +338,13 @@ enum PoolBond { Join, } +/// The type of account being created. +#[derive(Encode, Decode)] +enum AccountType { + Bonded, + Reward, +} + /// A delegator in a pool. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] @@ -480,14 +471,14 @@ impl BondedPool { /// Get the amount of points to issue for some new funds that will be bonded in the pool. fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { let bonded_balance = - T::StakingInterface::bonded_balance(&self.bonded_account()).unwrap_or(Zero::zero()); + T::StakingInterface::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); Pallet::::points_to_issue(bonded_balance, self.points, new_funds) } /// Get the amount of balance to unbond from the pool based on a delegator's points of the pool. fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { let bonded_balance = - T::StakingInterface::bonded_balance(&self.bonded_account()).unwrap_or(Zero::zero()); + T::StakingInterface::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); Pallet::::balance_to_unbond(bonded_balance, self.points, delegator_points) } @@ -521,11 +512,11 @@ impl BondedPool { self } - /// The pools balance that is not locked. This assumes the staking system is the only - fn non_locked_balance(&self) -> BalanceOf { + /// The pools balance that is transferrable. + fn transferrable_balance(&self) -> BalanceOf { let account = self.bonded_account(); T::Currency::free_balance(&account) - .saturating_sub(T::StakingInterface::locked_balance(&account).unwrap_or_default()) + .saturating_sub(T::StakingInterface::active_stake(&account).unwrap_or_default()) } fn can_nominate(&self, who: &T::AccountId) -> bool { @@ -555,7 +546,7 @@ impl BondedPool { ensure!(!self.is_destroying(), Error::::CanNotChangeState); let bonded_balance = - T::StakingInterface::bonded_balance(&self.bonded_account()).unwrap_or(Zero::zero()); + T::StakingInterface::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); let points_to_balance_ratio_floor = self @@ -747,7 +738,6 @@ pub struct RewardPool { impl RewardPool { /// Mutate the reward pool by updating the total earnings and current free balance. fn update_total_earnings_and_balance(&mut self, id: PoolId) { - // TODO: not happy with this. let current_balance = T::Currency::free_balance(&Pallet::::create_reward_account(id)); // The earnings since the last time it was updated let new_earnings = current_balance.saturating_sub(self.balance); @@ -883,8 +873,6 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: weights::WeightInfo; - // TODO: Should this just be part of the StakingInterface trait? We want the currencies to - // be the same anyways. /// The nominating balance. type Currency: Currency; @@ -902,7 +890,6 @@ pub mod pallet { type StakingInterface: StakingInterface< Balance = BalanceOf, AccountId = Self::AccountId, - LookupSource = ::Source, >; /// The amount of eras a `SubPools::with_era` pool can exist before it gets merged into the @@ -1150,8 +1137,10 @@ pub mod pallet { Ok(()) } - /// Unbond _all_ of the `target`'s funds from the pool. Under certain conditions, this call - /// can be dispatched permissionlessly (i.e. by any account). + /// Unbond _all_ of the `delegator_account`'s funds from the pool. + /// + /// Under certain conditions, this call can be dispatched permissionlessly (i.e. by any + /// account). /// /// # Conditions for a permissionless dispatch /// @@ -1170,6 +1159,7 @@ pub mod pallet { /// Note: If there are too many unlocking chunks to unbond with the pool account, /// [`Self::withdraw_unbonded_pool`] can be called to try and minimize unlocking chunks. #[pallet::weight(T::WeightInfo::unbond_other())] + #[frame_support::transactional] pub fn unbond_other( origin: OriginFor, delegator_account: T::AccountId, @@ -1181,19 +1171,11 @@ pub mod pallet { .defensive_ok_or_else(|| Error::::PoolNotFound)?; bonded_pool.ok_to_unbond_other_with(&caller, &delegator_account, &delegator)?; - // alternative: do_reward_payout can report if it changed states. - let was_destroying = bonded_pool.is_destroying(); - // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the bonded pool and thus they can no longer claim their payouts. // It is not strictly necessary to claim the rewards, but we do it here for UX. Self::do_reward_payout(delegator_account.clone(), &mut delegator, &mut bonded_pool)?; - // Note that we lazily create the unbonding pools here if they don't already exist - let sub_pools = SubPoolsStorage::::get(delegator.pool_id).unwrap_or_default(); - let current_era = T::StakingInterface::current_era(); - let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); - let balance_to_unbond = bonded_pool.balance_to_unbond(delegator.points); // Update the bonded pool. Note that we must do this *after* calculating the balance @@ -1201,16 +1183,19 @@ pub mod pallet { bonded_pool.points = bonded_pool.points.saturating_sub(delegator.points); // Unbond in the actual underlying pool - // TODO: can fail after write: better make it transactional. T::StakingInterface::unbond(bonded_pool.bonded_account(), balance_to_unbond)?; + // Note that we lazily create the unbonding pools here if they don't already exist + let sub_pools = SubPoolsStorage::::get(delegator.pool_id).unwrap_or_default(); + let current_era = T::StakingInterface::current_era(); + let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); + // Merge any older pools into the general, era agnostic unbond pool. Note that we do // this before inserting to ensure we don't go over the max unbonding pools. let mut sub_pools = sub_pools.maybe_merge_pools(unbond_era); - // Update the unbond pool associated with the current era with the - // unbonded funds. Note that we lazily create the unbond pool if it - // does not yet exist. + // Update the unbond pool associated with the current era with the unbonded funds. Note + // that we lazily create the unbond pool if it does not yet exist. sub_pools.unchecked_with_era_get_or_make(unbond_era).issue(balance_to_unbond); delegator.unbonding_era = Some(unbond_era); @@ -1221,14 +1206,6 @@ pub mod pallet { amount: balance_to_unbond, }); - if bonded_pool.is_destroying() && !was_destroying { - // Persist the pool to storage iff the state was updated - Self::deposit_event(Event::::State { - pool_id: delegator.pool_id, - new_state: PoolState::Destroying, - }); - } - // Now that we know everything has worked write the items to storage. bonded_pool.put(); SubPoolsStorage::insert(&delegator.pool_id, sub_pools); @@ -1334,7 +1311,7 @@ pub mod pallet { // This check is also defensive in cases where the unbond pool does not update its // balance (e.g. a bug in the slashing hook.) We gracefully proceed in // order to ensure delegators can leave the pool and it can be destroyed. - .min(bonded_pool.non_locked_balance()); + .min(bonded_pool.transferrable_balance()); T::Currency::transfer( &bonded_pool.bonded_account(), @@ -1368,7 +1345,7 @@ pub mod pallet { Zero::zero() ); debug_assert_eq!( - T::StakingInterface::locked_balance(&bonded_pool.bonded_account()) + T::StakingInterface::total_stake(&bonded_pool.bonded_account()) .unwrap_or_default(), Zero::zero() ); @@ -1462,7 +1439,7 @@ pub mod pallet { pub fn nominate( origin: OriginFor, pool_id: PoolId, - validators: Vec<::Source>, + validators: Vec, ) -> DispatchResult { let who = ensure_signed(origin)?; let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; @@ -1568,6 +1545,12 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn integrity_test() { + assert!( + sp_std::mem::size_of::() >= + 2 * sp_std::mem::size_of::>(), + "bit-length of the reward points must be at least twice as much as balance" + ); + assert!( T::StakingInterface::bonding_duration() < TotalUnbondingPools::::get(), "There must be more unbonding pools then the bonding duration / @@ -1580,16 +1563,15 @@ pub mod pallet { impl Pallet { /// Create the main, bonded account of a pool with the given id. - pub fn create_bonded_account(id: PoolId) -> T::AccountId { - // 4bytes ++ 8bytes ++ 1byte ++ 4bytes - T::PalletId::get().into_sub_account((1u8, id)) + fn create_bonded_account(id: PoolId) -> T::AccountId { + T::PalletId::get().into_sub_account((AccountType::Bonded, id)) } /// Create the reward account of a pool with the given id. - pub fn create_reward_account(id: PoolId) -> T::AccountId { - // TODO: integrity check for what is the reasonable max number of pools based on this. - // 4bytes ++ 8bytes ++ 1byte ++ 4bytes - T::PalletId::get().into_sub_account((2u8, id)) + fn create_reward_account(id: PoolId) -> T::AccountId { + // NOTE: in order to have a distinction in the test account id type (u128), we put account_type first so + // it does not get truncated out. + T::PalletId::get().into_sub_account((AccountType::Reward, id)) } /// Calculate the number of points to issue from a pool as `(current_points / current_balance) * @@ -1637,6 +1619,9 @@ impl Pallet { } /// Calculate the rewards for `delegator`. + /// + /// Returns the payout amount, and whether the pool state has been switched to destroying during + /// this call. fn calculate_delegator_payout( bonded_pool: &mut BondedPool, reward_pool: &mut RewardPool, @@ -1693,9 +1678,6 @@ impl Pallet { }; // Record updates - if reward_pool.total_earnings == BalanceOf::::max_value() { - bonded_pool.state = PoolState::Destroying; - } delegator.reward_pool_total_earnings = reward_pool.total_earnings; reward_pool.points = current_points.saturating_sub(delegator_virtual_points); reward_pool.balance = reward_pool.balance.saturating_sub(delegator_payout); @@ -1706,7 +1688,7 @@ impl Pallet { /// If the delegator has some rewards, transfer a payout from the reward pool to the delegator. /// /// # Note - /// + // TODO: revise this. /// This will persist updates for the reward pool to storage. But it will *not* persist updates /// to the `delegator` or `bonded_pool` to storage, that is the responsibility of the caller. fn do_reward_payout( @@ -1729,6 +1711,16 @@ impl Pallet { ExistenceRequirement::AllowDeath, )?; + if reward_pool.total_earnings == BalanceOf::::max_value() && + bonded_pool.state != PoolState::Destroying + { + bonded_pool.state = PoolState::Destroying; + Self::deposit_event(Event::::State { + pool_id: delegator.pool_id, + new_state: PoolState::Destroying, + }); + } + Self::deposit_event(Event::::PaidOut { delegator: delegator_account, pool_id: delegator.pool_id, @@ -1740,6 +1732,106 @@ impl Pallet { Ok(()) } + + /// Ensure the correctness of the state of this pallet. + /// + /// This should be valid before or after each state transition of this pallet. + /// + /// ## Invariants: + /// + /// First, let's consider pools: + /// + /// * `BondedPools` and `RewardPools` must all have the EXACT SAME key-set. + /// * `SubPoolsStorage` must be a subset of the above superset. + /// * `Metadata` keys must be a subset of the above superset. + /// * the count of the above set must be less than `MaxPools`. + /// + /// Then, considering delegators as well: + /// + /// * each `BondedPool.delegator_counter` must be: + /// - correct (compared to actual count of delegator who have `.pool_id` this pool) + /// - less than `MaxDelegatorsPerPool`. + /// * each `delegator.pool_id` must correspond to an existing `BondedPool.id` (which implies the + /// existence of the reward pool as well). + /// * count of all delegators must be less than `MaxDelegators`. + /// + /// Then, considering unbonding delegators: + /// + /// for each pool: + /// * sum of the balance that's tracked in all unbonding pools must be the same as the + /// unbonded balance of the main account, as reported by the staking interface. + /// * sum of the balance that's tracked in all unbonding pools, plus the bonded balance of the + /// main account should be less than or qual to the total balance of the main account. + /// + /// ## Sanity check level + /// + /// To cater for tests that want to escape parts of these checks, this function is split into + /// multiple `level`s, where the higher the level, the more checks we performs. So, + /// `sanity_check(255)` is the strongest sanity check, and `0` performs no checks. + #[cfg(any(test, debug_assertions))] + pub fn sanity_checks(level: u8) -> Result<(), &'static str> { + if level.is_zero() { + return Ok(()) + } + // note: while a bit wacky, since they have the same key, even collecting to vec should + // result in the same set of keys, in the same order. + let bonded_pools = BondedPools::::iter_keys().collect::>(); + let reward_pools = RewardPools::::iter_keys().collect::>(); + assert_eq!(bonded_pools, reward_pools); + + assert!(Metadata::::iter_keys().all(|k| bonded_pools.contains(&k))); + assert!(SubPoolsStorage::::iter_keys().all(|k| bonded_pools.contains(&k))); + + assert!(MaxPools::::get().map_or(true, |max| bonded_pools.len() <= (max as usize))); + + let mut pools_delegators = BTreeMap::::new(); + let mut all_delegators = 0u32; + Delegators::::iter().for_each(|(_, d)| { + assert!(BondedPools::::contains_key(d.pool_id)); + *pools_delegators.entry(d.pool_id).or_default() += 1; + all_delegators += 1; + }); + + BondedPools::::iter().for_each(|(id, bonded_pool)| { + assert_eq!( + pools_delegators.get(&id).map(|x| *x).unwrap_or_default(), + bonded_pool.delegator_counter + ); + assert!(MaxDelegatorsPerPool::::get() + .map_or(true, |max| bonded_pool.delegator_counter <= max)); + }); + assert!(MaxDelegators::::get().map_or(true, |max| all_delegators <= max)); + + if level <= u8::MAX / 2 { + return Ok(()) + } + + for (pool_id, _) in BondedPools::::iter() { + let pool_account = Pallet::::create_bonded_account(pool_id); + let subs = SubPoolsStorage::::get(pool_id).unwrap_or_default(); + let sum_unbonding_balance = subs + .with_era + .into_iter() + .map(|(_, v)| v) + .chain(sp_std::iter::once(subs.no_era)) + .map(|unbond_pool| unbond_pool.balance) + .fold(Zero::zero(), |a, b| a + b); + + let bonded_balance = + T::StakingInterface::active_stake(&pool_account).unwrap_or_default(); + let total_balance = T::Currency::total_balance(&pool_account); + + assert!( + total_balance >= bonded_balance + sum_unbonding_balance, + "total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}", + total_balance, + bonded_balance, + sum_unbonding_balance + ); + } + + Ok(()) + } } impl OnStakerSlash> for Pallet { diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 0216df5a01faf..d254eafb80020 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -34,7 +34,6 @@ impl StakingMock { impl sp_staking::StakingInterface for StakingMock { type Balance = Balance; type AccountId = AccountId; - type LookupSource = Self::AccountId; fn minimum_bond() -> Self::Balance { 10 @@ -48,11 +47,11 @@ impl sp_staking::StakingInterface for StakingMock { BondingDuration::get() } - fn bonded_balance(who: &Self::AccountId) -> Option { + fn active_stake(who: &Self::AccountId) -> Option { BondedBalanceMap::get().get(who).map(|v| *v) } - fn locked_balance(who: &Self::AccountId) -> Option { + fn total_stake(who: &Self::AccountId) -> Option { match ( UnbondingBalanceMap::get().get(who).map(|v| *v), BondedBalanceMap::get().get(who).map(|v| *v), @@ -92,7 +91,7 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } - fn nominate(_: Self::AccountId, nominations: Vec) -> DispatchResult { + fn nominate(_: Self::AccountId, nominations: Vec) -> DispatchResult { Nominations::set(nominations); Ok(()) } @@ -158,6 +157,7 @@ impl Convert for U256ToBalance { parameter_types! { pub static PostUnbondingPoolsWindow: u32 = 2; pub static MaxMetadataLen: u32 = 2; + pub static CheckLevel: u8 = 255; pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); } impl pools::Config for Runtime { @@ -198,6 +198,11 @@ impl ExtBuilder { self } + pub(crate) fn with_check(self, level: u8) -> Self { + CheckLevel::set(level); + self + } + pub(crate) fn build(self) -> sp_io::TestExternalities { // sp_tracing::try_init_simple(); let mut storage = @@ -233,13 +238,7 @@ impl ExtBuilder { pub fn build_and_execute(self, test: impl FnOnce() -> ()) { self.build().execute_with(|| { test(); - crate::sanity::checks::(); - }) - } - - pub fn build_and_execute_no_checks(self, test: impl FnOnce() -> ()) { - self.build().execute_with(|| { - test(); + Pools::sanity_checks(CheckLevel::get()).unwrap(); }) } } diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 0a024a298623c..11aa5bd0442c7 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -407,7 +407,7 @@ mod join { #[test] fn join_errors_correctly() { - ExtBuilder::default().build_and_execute_no_checks(|| { + ExtBuilder::default().with_check(0).build_and_execute(|| { // 10 is already part of the default pool created. assert_eq!(Delegators::::get(&10).unwrap().pool_id, 1); @@ -1322,7 +1322,7 @@ mod unbond { } ); - assert_eq!(StakingMock::bonded_balance(&default_bonded_account()).unwrap(), 0); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); }); } @@ -1357,7 +1357,7 @@ mod unbond { } ); - assert_eq!(StakingMock::bonded_balance(&default_bonded_account()).unwrap(), 94); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 94); assert_eq!(Delegators::::get(40).unwrap().unbonding_era, Some(0 + 3)); assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding @@ -1382,7 +1382,7 @@ mod unbond { } } ); - assert_eq!(StakingMock::bonded_balance(&default_bonded_account()).unwrap(), 2); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 2); assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0 + 3)); assert_eq!(Balances::free_balance(&550), 550 + 550); @@ -1406,7 +1406,7 @@ mod unbond { } } ); - assert_eq!(StakingMock::bonded_balance(&default_bonded_account()).unwrap(), 0); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0 + 3)); assert_eq!(Balances::free_balance(&550), 550 + 550); }); @@ -1414,7 +1414,7 @@ mod unbond { #[test] fn unbond_other_merges_older_pools() { - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().with_check(1).build_and_execute(|| { // Given assert_eq!(StakingMock::bonding_duration(), 3); SubPoolsStorage::::insert( @@ -1487,7 +1487,7 @@ mod unbond { } } ); - assert_eq!(StakingMock::bonded_balance(&default_bonded_account()).unwrap(), 10); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 10); assert_eq!( SubPoolsStorage::::get(1).unwrap(), SubPools { @@ -1555,7 +1555,7 @@ mod unbond { } } ); - assert_eq!(StakingMock::bonded_balance(&default_bonded_account()).unwrap(), 0); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); assert_eq!( UNBONDING_BALANCE_MAP .with(|m| *m.borrow_mut().get(&default_bonded_account()).unwrap()), @@ -1622,16 +1622,16 @@ mod pool_withdraw_unbonded { // Given 10 unbond'ed directly against the pool account assert_ok!(StakingMock::unbond(default_bonded_account(), 5)); // and the pool account only has 10 balance - assert_eq!(StakingMock::bonded_balance(&default_bonded_account()), Some(5)); - assert_eq!(StakingMock::locked_balance(&default_bonded_account()), Some(10)); + assert_eq!(StakingMock::active_stake(&default_bonded_account()), Some(5)); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(10)); assert_eq!(Balances::free_balance(&default_bonded_account()), 10); // When assert_ok!(Pools::pool_withdraw_unbonded(Origin::signed(10), 1, 0)); // Then there unbonding balance is no longer locked - assert_eq!(StakingMock::bonded_balance(&default_bonded_account()), Some(5)); - assert_eq!(StakingMock::locked_balance(&default_bonded_account()), Some(5)); + assert_eq!(StakingMock::active_stake(&default_bonded_account()), Some(5)); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(5)); assert_eq!(Balances::free_balance(&default_bonded_account()), 10); }); } @@ -1739,7 +1739,7 @@ mod withdraw_unbonded_other { // Given StakingMock::set_bonded_balance(default_bonded_account(), 100); // slash bonded balance Balances::make_free_balance_be(&default_bonded_account(), 100); - assert_eq!(StakingMock::locked_balance(&default_bonded_account()), Some(100)); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(100)); assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); @@ -1825,7 +1825,7 @@ mod withdraw_unbonded_other { #[test] fn withdraw_unbonded_other_errors_correctly() { - ExtBuilder::default().build_and_execute_no_checks(|| { + ExtBuilder::default().with_check(0).build_and_execute(|| { // Insert the sub-pool let sub_pools = SubPools { no_era: Default::default(), @@ -2099,7 +2099,7 @@ mod create { assert!(!BondedPools::::contains_key(2)); assert!(!RewardPools::::contains_key(2)); assert!(!Delegators::::contains_key(11)); - assert_eq!(StakingMock::bonded_balance(&next_pool_stash), None); + assert_eq!(StakingMock::active_stake(&next_pool_stash), None); Balances::make_free_balance_be(&11, StakingMock::minimum_bond()); assert_ok!(Pools::create( @@ -2138,7 +2138,7 @@ mod create { } ); assert_eq!( - StakingMock::bonded_balance(&next_pool_stash).unwrap(), + StakingMock::active_stake(&next_pool_stash).unwrap(), StakingMock::minimum_bond() ); assert_eq!( @@ -2154,7 +2154,7 @@ mod create { #[test] fn create_errors_correctly() { - ExtBuilder::default().build_and_execute_no_checks(|| { + ExtBuilder::default().with_check(0).build_and_execute(|| { assert_noop!( Pools::create(Origin::signed(10), 420, 123, 456, 789), Error::::AccountBelongsToOtherPool diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 593a362f8d9a1..686b023bc91d3 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1318,7 +1318,6 @@ impl SortedListProvider for UseNominatorsMap { impl StakingInterface for Pallet { type AccountId = T::AccountId; type Balance = BalanceOf; - type LookupSource = ::Source; fn minimum_bond() -> Self::Balance { MinNominatorBond::::get() @@ -1332,11 +1331,11 @@ impl StakingInterface for Pallet { Self::current_era().unwrap_or(Zero::zero()) } - fn bonded_balance(controller: &Self::AccountId) -> Option { + fn active_stake(controller: &Self::AccountId) -> Option { Self::ledger(controller).map(|l| l.active) } - fn locked_balance(controller: &Self::AccountId) -> Option { + fn total_stake(controller: &Self::AccountId) -> Option { Self::ledger(controller).map(|l| l.total) } @@ -1375,7 +1374,8 @@ impl StakingInterface for Pallet { ) } - fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult { + fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult { + let targets = targets.into_iter().map(T::Lookup::unlookup).collect::>(); Self::nominate(RawOrigin::Signed(controller).into(), targets) } } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index a49bfa8d41548..24cf794ee8436 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -61,9 +61,6 @@ pub trait StakingInterface { /// AccountId type used by the staking system type AccountId; - /// The type for the `validators` argument to `Self::nominate`. - type LookupSource; - /// The minimum amount required to bond in order to be a nominator. This does not necessarily /// mean the nomination will be counted in an election, but instead just enough to be stored as /// a nominator. In other words, this is the minimum amount to register the intention to @@ -82,17 +79,17 @@ pub trait StakingInterface { /// This should be the latest planned era that the staking system knows about. fn current_era() -> EraIndex; - /// Balance `controller` has bonded for nominating. - fn bonded_balance(controller: &Self::AccountId) -> Option; + /// The amount of active stake that `controller` has in the staking system. + fn active_stake(controller: &Self::AccountId) -> Option; - /// Balance the _Stash_ linked to `controller` has locked by the staking system. This should - /// include both the users bonded funds and their unlocking funds. + /// The total stake that `controller` has in the staking system. This includes the + /// [`active_stake`], and any funds currently in the process of unbonding via [`unbond`]. /// /// # Note /// /// This is only guaranteed to reflect the amount locked by the staking system. If there are /// non-staking locks on the bonded pair's balance this may not be accurate. - fn locked_balance(controller: &Self::AccountId) -> Option; + fn total_stake(controller: &Self::AccountId) -> Option; /// Bond (lock) `value` of `stash`'s balance. `controller` will be set as the account /// controlling `stash`. This creates what is referred to as "bonded pair". @@ -106,7 +103,7 @@ pub trait StakingInterface { /// Have `controller` nominate `validators`. fn nominate( controller: Self::AccountId, - validators: sp_std::vec::Vec, + validators: sp_std::vec::Vec, ) -> DispatchResult; /// Bond some extra amount in the _Stash_'s free balance against the active bonded balance of From ad7421a780c43ec4451b8d6a18331e8f02177e43 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Tue, 15 Mar 2022 17:58:46 +0000 Subject: [PATCH 185/299] nomination-pools: Fix state event regression + benches (#11045) * new sanity checks, few other changes * Fix benches, improve sanity check * Remove useless clear storage in benchmarking * Set state * Save * Doc * Update frame/nomination-pools/src/lib.rs Co-authored-by: kianenigma --- .../nomination-pools/benchmarking/src/lib.rs | 36 ++++--------------- frame/nomination-pools/src/lib.rs | 30 ++++++++++------ frame/nomination-pools/src/mock.rs | 1 - 3 files changed, 25 insertions(+), 42 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 6f1defe043dff..6d6187c9dce6f 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -15,7 +15,7 @@ use pallet_nomination_pools::{ MaxDelegatorsPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, Pallet as Pools, PoolRoles, PoolState, RewardPools, SubPoolsStorage, }; -use sp_runtime::traits::{Bounded, StaticLookup, Zero}; +use sp_runtime::traits::{Bounded, Zero}; use sp_staking::{EraIndex, StakingInterface}; // `frame_benchmarking::benchmarks!` macro needs this use pallet_nomination_pools::Call; @@ -32,13 +32,6 @@ pub trait Config: pub struct Pallet(Pools); -fn clear_storage() { - pallet_nomination_pools::BondedPools::::remove_all(); - pallet_nomination_pools::RewardPools::::remove_all(); - pallet_nomination_pools::SubPoolsStorage::::remove_all(); - pallet_nomination_pools::Delegators::::remove_all(); -} - fn create_funded_user_with_balance( string: &'static str, n: u32, @@ -123,13 +116,13 @@ impl ListScenario { T::StakingInterface::nominate( pool_origin1.clone(), // NOTE: these don't really need to be validators. - vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], + vec![account("random_validator", 0, USER_SEED)], )?; let (_, pool_origin2) = create_pool_account::(USER_SEED + 2, origin_weight); T::StakingInterface::nominate( pool_origin2.clone(), - vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))].clone(), + vec![account("random_validator", 0, USER_SEED)].clone(), )?; // Find a destination weight that will trigger the worst case scenario @@ -146,7 +139,7 @@ impl ListScenario { let (_, pool_dest1) = create_pool_account::(USER_SEED + 3, dest_weight); T::StakingInterface::nominate( pool_dest1.clone(), - vec![T::Lookup::unlookup(account("random_validator", 0, USER_SEED))], + vec![account("random_validator", 0, USER_SEED)], )?; let weight_of = pallet_staking::Pallet::::weight_of_fn(); @@ -197,8 +190,6 @@ impl ListScenario { frame_benchmarking::benchmarks! { join { - clear_storage::(); - let origin_weight = pallet_nomination_pools::MinCreateBond::::get() .max(CurrencyOf::::minimum_balance()) * 2u32.into(); @@ -227,8 +218,6 @@ frame_benchmarking::benchmarks! { } claim_payout { - clear_storage::(); - let origin_weight = pallet_nomination_pools::MinCreateBond::::get().max(CurrencyOf::::minimum_balance()) * 2u32.into(); let (depositor, pool_account) = create_pool_account::(0, origin_weight); @@ -257,8 +246,6 @@ frame_benchmarking::benchmarks! { } unbond_other { - clear_storage::(); - // the weight the nominator will start at. The value used here is expected to be // significantly higher than the first position in a list (e.g. the first bag threshold). let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) @@ -285,7 +272,6 @@ frame_benchmarking::benchmarks! { pool_withdraw_unbonded { let s in 0 .. MAX_SPANS; - clear_storage::(); let min_create_bond = MinCreateBond::::get() .max(T::StakingInterface::minimum_bond()) @@ -330,7 +316,6 @@ frame_benchmarking::benchmarks! { withdraw_unbonded_other_update { let s in 0 .. MAX_SPANS; - clear_storage::(); let min_create_bond = MinCreateBond::::get() .max(T::StakingInterface::minimum_bond()) @@ -378,7 +363,6 @@ frame_benchmarking::benchmarks! { withdraw_unbonded_other_kill { let s in 0 .. MAX_SPANS; - clear_storage::(); let min_create_bond = MinCreateBond::::get() .max(T::StakingInterface::minimum_bond()) @@ -447,8 +431,6 @@ frame_benchmarking::benchmarks! { } create { - clear_storage::(); - let min_create_bond = MinCreateBond::::get() .max(T::StakingInterface::minimum_bond()) .max(CurrencyOf::::minimum_balance()); @@ -488,14 +470,12 @@ frame_benchmarking::benchmarks! { } ); assert_eq!( - T::StakingInterface::active_balance(&Pools::::create_bonded_account(1)), + T::StakingInterface::active_stake(&Pools::::create_bonded_account(1)), Some(min_create_bond) ); } nominate { - clear_storage::(); - // Create a pool let min_create_bond = MinCreateBond::::get() .max(T::StakingInterface::minimum_bond()) @@ -505,9 +485,7 @@ frame_benchmarking::benchmarks! { // Create some accounts to nominate. For the sake of benchmarking they don't need to be // actual validators let validators: Vec<_> = (0..T::MaxNominations::get()) - .map(|i| - T::Lookup::unlookup(account("stash", USER_SEED, i)) - ) + .map(|i| account("stash", USER_SEED, i)) .collect(); whitelist_account!(depositor); @@ -555,8 +533,6 @@ frame_benchmarking::benchmarks! { } set_metadata { - clear_storage::(); - // Create a pool let min_create_bond = MinCreateBond::::get() .max(T::StakingInterface::minimum_bond()) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index ba4bb8692eea5..714920ae4693d 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -709,11 +709,20 @@ impl BondedPool { /// number saturating indicates the pool can no longer correctly keep track of state. fn bound_check(&mut self, n: U256) -> U256 { if n == U256::max_value() { - self.state = PoolState::Destroying + self.set_state(PoolState::Destroying) } n } + + // Set the state of `self`, and deposit an event if the state changed. State should never be set directly in + // in order to ensure a state change event is always correctly deposited. + fn set_state(&mut self, state: PoolState) { + if self.state != state { + self.state = state; + Pallet::::deposit_event(Event::::State { pool_id: self.id, new_state: state }); + }; + } } /// A reward pool. @@ -1462,19 +1471,14 @@ pub mod pallet { // The downside is that this seems like a misleading API if bonded_pool.can_toggle_state(&who) { - bonded_pool.state = state + bonded_pool.set_state(state); } else if bonded_pool.ok_to_be_open().is_err() && state == PoolState::Destroying { // If the pool has bad properties, then anyone can set it as destroying - bonded_pool.state = PoolState::Destroying; + bonded_pool.set_state(PoolState::Destroying); } else { Err(Error::::CanNotChangeState)?; } - Self::deposit_event(Event::::State { - pool_id, - new_state: bonded_pool.state.clone(), - }); - bonded_pool.put(); Ok(()) @@ -1563,12 +1567,13 @@ pub mod pallet { impl Pallet { /// Create the main, bonded account of a pool with the given id. - fn create_bonded_account(id: PoolId) -> T::AccountId { + + pub fn create_bonded_account(id: PoolId) -> T::AccountId { T::PalletId::get().into_sub_account((AccountType::Bonded, id)) } /// Create the reward account of a pool with the given id. - fn create_reward_account(id: PoolId) -> T::AccountId { + pub fn create_reward_account(id: PoolId) -> T::AccountId { // NOTE: in order to have a distinction in the test account id type (u128), we put account_type first so // it does not get truncated out. T::PalletId::get().into_sub_account((AccountType::Reward, id)) @@ -1678,6 +1683,9 @@ impl Pallet { }; // Record updates + if reward_pool.total_earnings == BalanceOf::::max_value() { + bonded_pool.set_state(PoolState::Destroying); + }; delegator.reward_pool_total_earnings = reward_pool.total_earnings; reward_pool.points = current_points.saturating_sub(delegator_virtual_points); reward_pool.balance = reward_pool.balance.saturating_sub(delegator_payout); @@ -1802,7 +1810,7 @@ impl Pallet { }); assert!(MaxDelegators::::get().map_or(true, |max| all_delegators <= max)); - if level <= u8::MAX / 2 { + if level <= 1 { return Ok(()) } diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index d254eafb80020..08ed41e58369b 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -204,7 +204,6 @@ 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(); From 738b7bbd43eb7caaacb82eef81fce8981fea2580 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 15 Mar 2022 18:08:35 +0000 Subject: [PATCH 186/299] FMT --- frame/staking/src/pallet/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index a171a70bdb062..7c99537b51be6 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -176,7 +176,7 @@ pub mod pallet { #[pallet::constant] type MaxUnlockingChunks: Get; - /// A hook called when any staker is slashed. Mostly likely this can be a no-op unless + /// A hook called when any staker is slashed. Mostly likely this can be a no-op unless /// other pallets exist that are affected by slashing per-staker. type OnStakerSlash: sp_staking::OnStakerSlash>; From ad551f310cf4ab82978e369295679296ebc93eb0 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 15 Mar 2022 18:14:12 +0000 Subject: [PATCH 187/299] Try fill in all staking configs --- bin/node/runtime/src/lib.rs | 1 + frame/babe/src/mock.rs | 1 + frame/grandpa/src/mock.rs | 1 + frame/offences/benchmarking/src/mock.rs | 1 + frame/session/benchmarking/src/mock.rs | 1 + 5 files changed, 5 insertions(+) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 20b718e2fa8f7..b6abc40e66f98 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -559,6 +559,7 @@ impl pallet_staking::Config for Runtime { // Note that the aforementioned does not scale to a very large number of nominators. type SortedListProvider = BagsList; type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = (); type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; } diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 152ec5ab206e7..0518b0f3b6644 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -199,6 +199,7 @@ impl pallet_staking::Config for Test { type GenesisElectionProvider = Self::ElectionProvider; type SortedListProvider = pallet_staking::UseNominatorsMap; type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 9dac33f979841..aef87b66920e5 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -207,6 +207,7 @@ impl pallet_staking::Config for Test { type GenesisElectionProvider = Self::ElectionProvider; type SortedListProvider = pallet_staking::UseNominatorsMap; type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 22c9af0f4c3cd..5937f1cdc29af 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -177,6 +177,7 @@ impl pallet_staking::Config for Test { type GenesisElectionProvider = Self::ElectionProvider; type SortedListProvider = pallet_staking::UseNominatorsMap; type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index a9328b6546c91..593b532a303e6 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -183,6 +183,7 @@ impl pallet_staking::Config for Test { type GenesisElectionProvider = Self::ElectionProvider; type MaxUnlockingChunks = ConstU32<32>; type SortedListProvider = pallet_staking::UseNominatorsMap; + type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } From 49de9654fefb1e88bf61150ab0cae1c9da5b95d9 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 16 Mar 2022 10:37:54 +0000 Subject: [PATCH 188/299] Fix build --- frame/election-provider-support/src/lib.rs | 32 ---------------------- 1 file changed, 32 deletions(-) diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 58608eed666b5..1bd10ba09346f 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -264,38 +264,6 @@ pub trait ElectionDataProvider { fn clear() {} } -<<<<<<< HEAD -// TODO: [now] this isn't practically useful, we require the dataprovider for staking's election -// provider to be the staking pallet -/// An election data provider that should only be used for testing. -#[cfg(feature = "std")] -pub struct TestDataProvider(sp_std::marker::PhantomData); - -#[cfg(feature = "std")] -impl ElectionDataProvider for TestDataProvider<(AccountId, BlockNumber)> { - type AccountId = AccountId; - type BlockNumber = BlockNumber; - type MaxVotesPerVoter = (); - - fn targets(_maybe_max_len: Option) -> data_provider::Result> { - Ok(Default::default()) - } - - fn voters(_maybe_max_len: Option) -> data_provider::Result>> { - Ok(Default::default()) - } - - fn desired_targets() -> data_provider::Result { - Ok(Default::default()) - } - - fn next_election_prediction(now: BlockNumber) -> BlockNumber { - now - } -} - -======= ->>>>>>> 499acc0e31e06dee9110a9cc169cd7c52f500b10 /// Something that can compute the result of an election and pass it back to the caller. /// /// This trait only provides an interface to _request_ an election, i.e. From e0a09e09c4349adbb090e83ab426b250fe3bca2d Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 16 Mar 2022 12:35:13 +0000 Subject: [PATCH 189/299] More changes to nomination pools (#11050) * new sanity checks, few other changes * some last touches as a whole * Apply suggestions from code review * Remove redundant event * Improve unbond_other error handling * Remove comment Co-authored-by: Zeke Mostov Co-authored-by: emostov <32168567+emostov@users.noreply.github.com> --- .../nomination-pools/benchmarking/src/lib.rs | 2 +- .../nomination-pools/benchmarking/src/mock.rs | 3 +- frame/nomination-pools/src/lib.rs | 109 +++++++----------- .../support/src/storage/bounded_btree_map.rs | 8 ++ 4 files changed, 53 insertions(+), 69 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 6d6187c9dce6f..64a8eef6396df 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -127,7 +127,7 @@ impl ListScenario { // Find a destination weight that will trigger the worst case scenario let dest_weight_as_vote = - ::SortedListProvider::weight_update_worst_case( + ::SortedListProvider::score_update_worst_case( &pool_origin1, is_increase, ); diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 6dfc93f1a790b..9f0c7b09bf121 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -122,7 +122,8 @@ impl pallet_bags_list::Config for Runtime { type Event = Event; type WeightInfo = (); type BagThresholds = BagThresholds; - type VoteWeightProvider = Staking; + type ScoreProvider = Staking; + type Score = VoteWeight; } pub struct BalanceToU256; diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 714920ae4693d..04c029be03721 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -422,7 +422,6 @@ impl sp_std::ops::Deref for BondedPool { } } -// TODO: ask a rust guy if this is a bad thing to do. impl sp_std::ops::DerefMut for BondedPool { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner @@ -688,7 +687,6 @@ impl BondedPool { let points_issued = self.issue(amount); match ty { - // TODO: Consider making StakingInterface use reference. PoolBond::Create => T::StakingInterface::bond( self.bonded_account(), // We make the stash and controller the same for simplicity @@ -715,8 +713,8 @@ impl BondedPool { n } - // Set the state of `self`, and deposit an event if the state changed. State should never be set directly in - // in order to ensure a state change event is always correctly deposited. + // Set the state of `self`, and deposit an event if the state changed. State should never be set + // directly in in order to ensure a state change event is always correctly deposited. fn set_state(&mut self, state: PoolState) { if self.state != state { self.state = state; @@ -804,52 +802,30 @@ pub struct SubPools { impl SubPools { /// Merge the oldest `with_era` unbond pools into the `no_era` unbond pool. - // TODO: Consider not consuming self. + /// + /// This is often used whilst getting the sub-pool from storage, thus it consumes and returns + /// `Self` for ergonomic purposes. fn maybe_merge_pools(mut self, unbond_era: EraIndex) -> Self { - // TODO: remove - if unbond_era < TotalUnbondingPools::::get().into() { - // For the first `0..TotalUnbondingPools` eras of the chain we don't need to do - // anything. Ex: if `TotalUnbondingPools` is 5 and we are in era 4 we can add a pool - // for this era and have exactly `TotalUnbondingPools` pools. - return self - } - // Ex: if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools - // 6..=10. - let newest_era_to_remove = unbond_era.saturating_sub(TotalUnbondingPools::::get()); - - // TODO: eras to keep, one filter, tweak self.no_era whilst filtering. - let eras_to_remove: Vec<_> = self - .with_era - .keys() - .cloned() - .filter(|era| *era <= newest_era_to_remove) - .collect(); - for era in eras_to_remove { - if let Some(p) = self.with_era.remove(&era).defensive() { - self.no_era.points = self.no_era.points.saturating_add(p.points); - self.no_era.balance = self.no_era.balance.saturating_add(p.balance); - } + // 6..=10. Note that in the first few eras where `checked_sub` is `None`, we don't remove + // anything. + if let Some(newest_era_to_remove) = unbond_era.checked_sub(TotalUnbondingPools::::get()) + { + self.with_era.retain(|k, v| { + if *k > newest_era_to_remove { + // keep + true + } else { + // merge into the no-era pool + self.no_era.points = self.no_era.points.saturating_add(v.points); + self.no_era.balance = self.no_era.balance.saturating_add(v.balance); + false + } + }); } self } - - /// Get the unbond pool for `era`. If one does not exist a default entry will be inserted. - /// - /// The caller must ensure that the `SubPools::with_era` has room for 1 more entry. Calling - /// [`SubPools::maybe_merge_pools`] with the current era should the sub pools are in an ok state - /// to call this method. - // TODO: dissolve and move to call site. - fn unchecked_with_era_get_or_make(&mut self, era: EraIndex) -> &mut UnbondPool { - if !self.with_era.contains_key(&era) { - self.with_era - .try_insert(era, UnbondPool::default()) - .expect("caller has checked pre-conditions. qed."); - } - - self.with_era.get_mut(&era).expect("entry inserted on the line above. qed.") - } } /// The maximum amount of eras an unbonding pool can exist prior to being merged with the @@ -1065,6 +1041,8 @@ pub mod pallet { DoesNotHavePermission, /// Metadata exceeds [`T::MaxMetadataLen`] MetadataExceedsMaxLen, + /// Some error occurred that should never happen. This should be reported to the maintainers. + DefensiveError, } #[pallet::call] @@ -1194,18 +1172,29 @@ pub mod pallet { // Unbond in the actual underlying pool T::StakingInterface::unbond(bonded_pool.bonded_account(), balance_to_unbond)?; - // Note that we lazily create the unbonding pools here if they don't already exist - let sub_pools = SubPoolsStorage::::get(delegator.pool_id).unwrap_or_default(); let current_era = T::StakingInterface::current_era(); let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); - - // Merge any older pools into the general, era agnostic unbond pool. Note that we do - // this before inserting to ensure we don't go over the max unbonding pools. - let mut sub_pools = sub_pools.maybe_merge_pools(unbond_era); + // Note that we lazily create the unbonding pools here if they don't already exist + let mut sub_pools = SubPoolsStorage::::get(delegator.pool_id) + .unwrap_or_default() + .maybe_merge_pools(unbond_era); // Update the unbond pool associated with the current era with the unbonded funds. Note // that we lazily create the unbond pool if it does not yet exist. - sub_pools.unchecked_with_era_get_or_make(unbond_era).issue(balance_to_unbond); + if !sub_pools.with_era.contains_key(&unbond_era) { + sub_pools + .with_era + .try_insert(unbond_era, UnbondPool::default()) + // The above call to `maybe_merge_pools` should ensure there is + // always enough space to insert. + .defensive_map_err(|_| Error::::DefensiveError)?; + } + sub_pools + .with_era + .get_mut(&unbond_era) + // The above check ensures the pool exists. + .defensive_ok_or_else(|| Error::::DefensiveError)? + .issue(balance_to_unbond); delegator.unbonding_era = Some(unbond_era); @@ -1466,9 +1455,6 @@ pub mod pallet { let who = ensure_signed(origin)?; let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; ensure!(bonded_pool.state != PoolState::Destroying, Error::::CanNotChangeState); - // TODO: [now] we could check if bonded_pool.ok_to_be_open().is_err(), and if thats - // true always set the state to destroying, regardless of the stat the caller passes. - // The downside is that this seems like a misleading API if bonded_pool.can_toggle_state(&who) { bonded_pool.set_state(state); @@ -1574,8 +1560,8 @@ impl Pallet { /// Create the reward account of a pool with the given id. pub fn create_reward_account(id: PoolId) -> T::AccountId { - // NOTE: in order to have a distinction in the test account id type (u128), we put account_type first so - // it does not get truncated out. + // NOTE: in order to have a distinction in the test account id type (u128), we put + // account_type first so it does not get truncated out. T::PalletId::get().into_sub_account((AccountType::Reward, id)) } @@ -1696,7 +1682,6 @@ impl Pallet { /// If the delegator has some rewards, transfer a payout from the reward pool to the delegator. /// /// # Note - // TODO: revise this. /// This will persist updates for the reward pool to storage. But it will *not* persist updates /// to the `delegator` or `bonded_pool` to storage, that is the responsibility of the caller. fn do_reward_payout( @@ -1719,16 +1704,6 @@ impl Pallet { ExistenceRequirement::AllowDeath, )?; - if reward_pool.total_earnings == BalanceOf::::max_value() && - bonded_pool.state != PoolState::Destroying - { - bonded_pool.state = PoolState::Destroying; - Self::deposit_event(Event::::State { - pool_id: delegator.pool_id, - new_state: PoolState::Destroying, - }); - } - Self::deposit_event(Event::::PaidOut { delegator: delegator_account, pool_id: delegator.pool_id, diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index ed132adac657e..9d0463d658036 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -77,6 +77,14 @@ where Self(t, Default::default()) } + /// Exactly the same semantics as `BTreeMap::retain`. + /// + /// The is a safe `&mut self` borrow because `retain` can only ever decrease the length of the + /// inner map. + pub fn retain bool>(&mut self, f: F) { + self.0.retain(f) + } + /// Create a new `BoundedBTreeMap`. /// /// Does not allocate. From bde07d53fc8507a7f3225c2d540da42d65573ed7 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 16 Mar 2022 13:14:30 +0000 Subject: [PATCH 190/299] Remove sanity module and some TODOs --- frame/nomination-pools/src/lib.rs | 11 ++--------- frame/nomination-pools/src/sanity.rs | 19 ------------------- frame/nomination-pools/src/tests.rs | 3 --- 3 files changed, 2 insertions(+), 31 deletions(-) delete mode 100644 frame/nomination-pools/src/sanity.rs diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 04c029be03721..0b4fa9242c157 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -553,22 +553,17 @@ impl BondedPool { // We checked for zero above .div(bonded_balance); - // TODO make sure these checks make sense. Taken from staking design chat with Al - // Pool points can inflate relative to balance, but only if the pool is slashed. // If we cap the ratio of points:balance so one cannot join a pool that has been slashed // 90%, - ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, - // TODO instead of this, where we multiply and it could saturate, watch out for being close - // to the point of saturation. ensure!( bonded_balance < BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow - // `BalanceOf`. + // `BalanceOf`. Note that these are just heuristics. Ok(()) } @@ -834,7 +829,7 @@ impl SubPools { pub struct TotalUnbondingPools(PhantomData); impl Get for TotalUnbondingPools { fn get() -> u32 { - // TODO: This may be too dangerous in the scenario bonding_duration gets decreased because + // NOTE: this may be dangerous in the scenario bonding_duration gets decreased because // we would no longer be able to decode `SubPoolsWithEra`, which uses `TotalUnbondingPools` // as the bound T::StakingInterface::bonding_duration() + T::PostUnbondingPoolsWindow::get() @@ -1332,8 +1327,6 @@ pub mod pallet { // Kill accounts from storage by making their balance go below ED. We assume that // the accounts have no references that would prevent destruction once we get to // this point. - // TODO: in correct scenario, these two accounts should be zero when we reach there - // anyway. debug_assert_eq!( T::Currency::free_balance(&bonded_pool.reward_account()), Zero::zero() diff --git a/frame/nomination-pools/src/sanity.rs b/frame/nomination-pools/src/sanity.rs deleted file mode 100644 index 1a40110d8ee94..0000000000000 --- a/frame/nomination-pools/src/sanity.rs +++ /dev/null @@ -1,19 +0,0 @@ -use super::*; - -/// Sanity check all invariants -pub(crate) fn checks() { - assert_eq!(RewardPools::::count(), BondedPools::::count()); - assert!(SubPoolsStorage::::count() <= BondedPools::::count()); - assert!(Delegators::::count() >= BondedPools::::count()); - bonding_pools_checks::(); -} - -fn bonding_pools_checks() { - let mut delegators_seen = 0; - for (_account, pool) in BondedPools::::iter() { - assert!(pool.delegator_counter >= 1); - assert!(MaxDelegatorsPerPool::::get().map_or(true, |max| pool.delegator_counter <= max)); - delegators_seen += pool.delegator_counter; - } - assert_eq!(delegators_seen, Delegators::::count()); -} diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 11aa5bd0442c7..8b33de7da674c 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -475,7 +475,6 @@ mod join { #[test] #[should_panic = "Defensive failure has been triggered!"] fn join_panics_when_reward_pool_not_found() { - // TODO: but we should fail defensively.. ExtBuilder::default().build_and_execute(|| { StakingMock::set_bonded_balance(Pools::create_bonded_account(123), 100); BondedPool:: { @@ -1658,8 +1657,6 @@ mod withdraw_unbonded_other { assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); let mut sub_pools = SubPoolsStorage::::get(1).unwrap(); - // TODO: [now] in the future we could use StakingMock::unbond_era_for(current_era) - // instead of current_era + 3. let unbond_pool = sub_pools.with_era.get_mut(&(current_era + 3)).unwrap(); // Sanity check assert_eq!(*unbond_pool, UnbondPool { points: 10, balance: 10 }); From 3b975345d5b1060abe8f2be842ba2e1da0a2723d Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 16 Mar 2022 17:55:41 +0000 Subject: [PATCH 191/299] round of feedback and imp from kian --- frame/staking/src/lib.rs | 137 ++++++++++++++++---------------- frame/staking/src/pallet/mod.rs | 4 +- frame/staking/src/tests.rs | 98 +++++++++++++++-------- primitives/staking/src/lib.rs | 8 +- 4 files changed, 139 insertions(+), 108 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 1d1964a399feb..af7a059f66a4c 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -302,7 +302,7 @@ mod pallet; use codec::{Decode, Encode, HasCompact}; use frame_support::{ parameter_types, - traits::{Currency, Get}, + traits::{Currency, Defensive, Get}, weights::Weight, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; @@ -435,7 +435,7 @@ pub struct UnlockChunk { value: Balance, /// Era number at which point it'll be unlocked. #[codec(compact)] - era: EraIndex, + unlock_era: EraIndex, } /// The ledger of a (bonded) stash. @@ -481,7 +481,7 @@ impl StakingLedger { .unlocking .into_iter() .filter(|chunk| { - if chunk.era > current_era { + if chunk.unlock_era > current_era { true } else { total = total.saturating_sub(chunk.value); @@ -530,31 +530,30 @@ impl StakingLedger { (self, unlocking_balance) } - /// Slash the staker for a given amount of balance. This can grow the value - /// of the slash in the case that either the active bonded or some unlocking chunks become dust - /// after slashing. Returns the amount of funds actually slashed. + /// Slash the staker for a given amount of balance. This can grow the value of the slash in the + /// case that either the active bonded or some unlocking chunks become dust after slashing. + /// Returns the amount of funds actually slashed. /// /// Note that this calls `Config::OnStakerSlash::on_slash` with information as to how the slash /// was applied. - /// - /// Generally slashes are computed by: - /// - /// 1) Balances of the unlocking chunks in range `slash_era + 1..=apply_era` are summed and - /// stored in `total_balance_affected`. - /// - /// 2) `slash_ratio` is computed as `slash_amount / total_balance_affected`. - /// - /// 3) `Ledger::active` is set to `(1- slash_ratio) * Ledger::active`. - /// - /// 4) For all unlocking chunks created in range `slash_era + 1..=apply_era` set their balance - /// to `(1 - slash_ratio) * unbonding_pool_balance`. We start with slash_era + 1, because that - /// is the earliest the active sake while slash could be unbonded. - /// - /// 5) Slash any remaining slash amount from the remaining chunks, starting with the `slash_era` - /// and going backwards. - /// - /// There are some edge cases due to saturating arithmetic - see logic below and tests for - /// details. + // Generally slashes are computed by: + // + // 1) Balances of the unlocking chunks in range `slash_era + 1..=apply_era` are summed and + // stored in `total_balance_affected`. + // + // 2) `slash_ratio` is computed as `slash_amount / total_balance_affected`. + // + // 3) `Ledger::active` is set to `(1- slash_ratio) * Ledger::active`. + // + // 4) For all unlocking chunks created in range `slash_era + 1..=apply_era` set their balance + // to `(1 - slash_ratio) * unbonding_pool_balance`. We start with slash_era + 1, because that + // is the earliest the active sake while slash could be unbonded. + // + // 5) Slash any remaining slash amount from the remaining chunks, starting with the `slash_era` + // and going backwards. + // + // There are some edge cases due to saturating arithmetic - see logic below and tests for + // details. fn slash( &mut self, slash_amount: BalanceOf, @@ -564,6 +563,7 @@ impl StakingLedger { use sp_runtime::traits::CheckedMul as _; use sp_staking::OnStakerSlash as _; use sp_std::ops::Div as _; + if slash_amount.is_zero() { return Zero::zero() } @@ -572,77 +572,80 @@ impl StakingLedger { let pre_slash_total = self.total; let era_after_slash = slash_era + 1; - // When a user unbonds, the chunk is given the era `current_era + BondingDuration`. See - // logic in [`Self::unbond`]. - let era_of_chunks_created_after_slash = era_after_slash + T::BondingDuration::get(); - let start_index = - self.unlocking.partition_point(|c| c.era < era_of_chunks_created_after_slash); - // The indices of the first chunk after the slash up through the most recent chunk. - // (The most recent chunk is at greatest from this era) - let affected_indices = start_index..self.unlocking.len(); + // TODO: test to make sure chunks before the slash are never slashed. + // at this era onwards, the funds can be slashed. + let chunk_unlock_era_after_slash = era_after_slash + T::BondingDuration::get(); // Calculate the total balance of active funds and unlocking funds in the affected range. - let affected_balance = { - let unbonding_affected_balance = - affected_indices.clone().fold(BalanceOf::::zero(), |sum, i| { - if let Some(chunk) = self.unlocking.get_mut(i) { - sum.saturating_add(chunk.value) - } else { - sum - } - }); - self.active.saturating_add(unbonding_affected_balance) + let (affected_balance, slash_chunks_priority): (_, Box>) = { + if let Some(start_index) = + self.unlocking.iter().position(|c| c.unlock_era >= chunk_unlock_era_after_slash) + { + // The indices of the first chunk after the slash up through the most recent chunk. + // (The most recent chunk is at greatest from this era) + let affected_indices = start_index..self.unlocking.len(); + let unbonding_affected_balance = + affected_indices.clone().fold(BalanceOf::::zero(), |sum, i| { + if let Some(chunk) = self.unlocking.get_mut(i).defensive() { + sum.saturating_add(chunk.value) + } else { + sum + } + }); + ( + self.active.saturating_add(unbonding_affected_balance), + Box::new(affected_indices.chain((0..start_index).rev())), + ) + } else { + (self.active, Box::new((0..self.unlocking.len()).rev())) + } }; let is_proportional_slash = slash_amount < affected_balance; // Helper to update `target` and the ledgers total after accounting for slashing `target`. + // TODO: use PerThing let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { let maybe_numerator = slash_amount.checked_mul(target); - let slash_from_target = match (maybe_numerator, is_proportional_slash) { + let mut slash_from_target = match (maybe_numerator, is_proportional_slash) { // Equivalent to `(slash_amount / affected_balance) * target`. (Some(numerator), true) => numerator.div(affected_balance), // If the slash amount is gt than the affected balance OR the arithmetic to // calculate the proportion saturated, we just try to slash as much as possible. - (None, _) | (_, false) => (*target).min(*slash_remaining), - }; - - *target = *target - slash_from_target; // slash_from_target cannot be gt target. + (None, _) | (_, false) => *slash_remaining, + } + .min(*target); - let actual_slashed = if *target <= minimum_balance { + // finally, slash out from *target exactly `slash_from_target`. + *target = *target - slash_from_target; + if *target <= minimum_balance { // Slash the rest of the target if its dust - sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) - } else { - slash_from_target - }; + slash_from_target = + sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) + } - self.total = self.total.saturating_sub(actual_slashed); - *slash_remaining = slash_remaining.saturating_sub(actual_slashed); + // TODO: maybe move this outside of the closure to keep it a bit more pure. + self.total = self.total.saturating_sub(slash_from_target); + *slash_remaining = slash_remaining.saturating_sub(slash_from_target); }; // If this is *not* a proportional slash, the active will always wiped to 0. slash_out_of(&mut self.active, &mut remaining_slash); let mut slashed_unlocking = BTreeMap::<_, _>::new(); - let indices_to_slash - // First slash unbonding chunks from after the slash - = affected_indices - // Then start slashing older chunks, start from the era of the slash - .chain((0..start_index).rev()); - for i in indices_to_slash { - if let Some(chunk) = self.unlocking.get_mut(i) { + for i in slash_chunks_priority { + if let Some(chunk) = self.unlocking.get_mut(i).defensive() { slash_out_of(&mut chunk.value, &mut remaining_slash); - slashed_unlocking.insert(chunk.era, chunk.value); - + // write the new slashed value of this chunk to the map. + slashed_unlocking.insert(chunk.unlock_era, chunk.value); if remaining_slash.is_zero() { break } } else { - break // defensive, indices should always be in bounds. + break } } - + self.unlocking.retain(|c| !c.value.is_zero()); T::OnStakerSlash::on_slash(&self.stash, self.active, &slashed_unlocking); - pre_slash_total.saturating_sub(self.total) } } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 7c99537b51be6..775cf95d9ab18 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -907,7 +907,7 @@ pub mod pallet { // Note: in case there is no current era it is fine to bond one era more. let era = Self::current_era().unwrap_or(0) + T::BondingDuration::get(); if let Some(mut chunk) = - ledger.unlocking.last_mut().filter(|chunk| chunk.era == era) + ledger.unlocking.last_mut().filter(|chunk| chunk.unlock_era == era) { // To keep the chunk count down, we only keep one chunk per era. Since // `unlocking` is a FiFo queue, if a chunk exists for `era` we know that it will @@ -916,7 +916,7 @@ pub mod pallet { } else { ledger .unlocking - .try_push(UnlockChunk { value, era }) + .try_push(UnlockChunk { value, unlock_era: era }) .map_err(|_| Error::::NoMoreChunks)?; }; // NOTE: ledger must be updated prior to calling `Self::weight_of`. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index c847157c5c120..624c6c9003799 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -1257,7 +1257,7 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 1000, unlock_era: 2 + 3 }], claimed_rewards: vec![] }), ); @@ -1270,7 +1270,7 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 1000, unlock_era: 2 + 3 }], claimed_rewards: vec![] }), ); @@ -1286,7 +1286,7 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 1000, unlock_era: 2 + 3 }], claimed_rewards: vec![] }), ); @@ -1391,7 +1391,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 900, era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 900, unlock_era: 2 + 3 }], claimed_rewards: vec![], }) ); @@ -1417,7 +1417,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 900, era: 5 }], + unlocking: bounded_vec![UnlockChunk { value: 900, unlock_era: 5 }], claimed_rewards: vec![], }) ); @@ -1430,7 +1430,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 600, - unlocking: bounded_vec![UnlockChunk { value: 400, era: 5 }], + unlocking: bounded_vec![UnlockChunk { value: 400, unlock_era: 5 }], claimed_rewards: vec![], }) ); @@ -1458,7 +1458,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 900, era: 5 }], + unlocking: bounded_vec![UnlockChunk { value: 900, unlock_era: 5 }], claimed_rewards: vec![], }) ); @@ -1471,7 +1471,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 600, - unlocking: bounded_vec![UnlockChunk { value: 400, era: 5 }], + unlocking: bounded_vec![UnlockChunk { value: 400, unlock_era: 5 }], claimed_rewards: vec![], }) ); @@ -1513,7 +1513,7 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 600, - unlocking: bounded_vec![UnlockChunk { value: 400, era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 400, unlock_era: 2 + 3 }], claimed_rewards: vec![], }) ); @@ -1529,8 +1529,8 @@ fn rebond_is_fifo() { total: 1000, active: 300, unlocking: bounded_vec![ - UnlockChunk { value: 400, era: 2 + 3 }, - UnlockChunk { value: 300, era: 3 + 3 }, + UnlockChunk { value: 400, unlock_era: 2 + 3 }, + UnlockChunk { value: 300, unlock_era: 3 + 3 }, ], claimed_rewards: vec![], }) @@ -1547,9 +1547,9 @@ fn rebond_is_fifo() { total: 1000, active: 100, unlocking: bounded_vec![ - UnlockChunk { value: 400, era: 2 + 3 }, - UnlockChunk { value: 300, era: 3 + 3 }, - UnlockChunk { value: 200, era: 4 + 3 }, + UnlockChunk { value: 400, unlock_era: 2 + 3 }, + UnlockChunk { value: 300, unlock_era: 3 + 3 }, + UnlockChunk { value: 200, unlock_era: 4 + 3 }, ], claimed_rewards: vec![], }) @@ -1564,8 +1564,8 @@ fn rebond_is_fifo() { total: 1000, active: 500, unlocking: bounded_vec![ - UnlockChunk { value: 400, era: 2 + 3 }, - UnlockChunk { value: 100, era: 3 + 3 }, + UnlockChunk { value: 400, unlock_era: 2 + 3 }, + UnlockChunk { value: 100, unlock_era: 3 + 3 }, ], claimed_rewards: vec![], }) @@ -1595,7 +1595,7 @@ fn rebond_emits_right_value_in_event() { stash: 11, total: 1000, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 900, era: 1 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 900, unlock_era: 1 + 3 }], claimed_rewards: vec![], }) ); @@ -1608,7 +1608,7 @@ fn rebond_emits_right_value_in_event() { stash: 11, total: 1000, active: 200, - unlocking: bounded_vec![UnlockChunk { value: 800, era: 1 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 800, unlock_era: 1 + 3 }], claimed_rewards: vec![], }) ); @@ -1839,7 +1839,7 @@ fn bond_with_no_staked_value() { stash: 1, active: 0, total: 5, - unlocking: bounded_vec![UnlockChunk { value: 5, era: 3 }], + unlocking: bounded_vec![UnlockChunk { value: 5, unlock_era: 3 }], claimed_rewards: vec![], }) ); @@ -3919,7 +3919,7 @@ fn cannot_rebond_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 0, - unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, era: 3 }], + unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, unlock_era: 3 }], claimed_rewards: vec![] } ); @@ -3956,7 +3956,7 @@ fn cannot_bond_extra_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 0, - unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, era: 3 }], + unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, unlock_era: 3 }], claimed_rewards: vec![] } ); @@ -4804,7 +4804,7 @@ fn force_apply_min_commission_works() { #[test] fn ledger_slash_works() { - let c = |era, value| UnlockChunk:: { era, value }; + let c = |unlock_era, value| UnlockChunk:: { unlock_era, value }; // Given let mut ledger = StakingLedger:: { stash: 123, @@ -4840,7 +4840,7 @@ fn ledger_slash_works() { // When all the chunks overlap with the slash eras assert_eq!(ledger.slash(20, 0, 0), 20); // Then - assert_eq!(ledger.unlocking, vec![c(4, 0), c(5, 0)]); + assert_eq!(ledger.unlocking, vec![]); assert_eq!(ledger.total, 0); assert_eq!(LedgerSlashPerEra::get().0, 0); assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)])); @@ -4848,8 +4848,9 @@ fn ledger_slash_works() { // Given ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; ledger.active = 500; - ledger.total = 40 + 10 + 100 + 250 + 500; // 900 - // When we have a partial slash that touches all chunks + // 900 + ledger.total = 40 + 10 + 100 + 250 + 500; + // When we have a partial slash that touches all chunks assert_eq!(ledger.slash(900 / 2, 0, 0), 450); // Then assert_eq!(ledger.active, 500 / 2); @@ -4861,6 +4862,35 @@ fn ledger_slash_works() { BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)]) ); + // slash 1/4th with not chunk. + ledger.unlocking = bounded_vec![]; + ledger.active = 500; + ledger.total = 500; + // When we have a partial slash that touches all chunks + assert_eq!(ledger.slash(500 / 4, 0, 0), 500 / 4); + // Then + assert_eq!(ledger.active, 3 * 500 / 4); + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, ledger.active); + assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); + + // // slash 1/4th with chunks + // ledger.unlocking = bounded_vec![c(4, 40), c(5, 120), c(6, 80)]; + // ledger.active = 120; + // ledger.total = 40 + 120 + 80 + 120; // 240 + // // When we have a partial slash that touches all chunks + // assert_eq!(ledger.slash(240 / 4, 0, 0), 2 / 4 - 1); + // // Then + // assert_eq!(ledger.unlocking, vec![c(4, 3 * 40 / 4), c(5, 3 * 120 / 4), c(6, 3 * 80 / 4)]); + // assert_eq!(ledger.active, 3 * 120 / 4); + // assert_eq!(ledger.total, 3 * 240 / 4 + 1); + // assert_eq!(LedgerSlashPerEra::get().0, 3 * 240 / 4); + // assert_eq!( + // LedgerSlashPerEra::get().1, + // BTreeMap::from([(4, 3 * 40 / 4), (5, 3 * 80 / 4), (6, 3 * 120 / 4)]) + // ); + // Given we have the same as above, ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; ledger.active = 500; @@ -4876,10 +4906,10 @@ fn ledger_slash_works() { ), 475 ); - // Then + let dust = (10 / 2) + (40 / 2); assert_eq!(ledger.active, 500 / 2); - assert_eq!(ledger.unlocking, vec![c(4, 0), c(5, 100 / 2), c(6, 0), c(7, 250 / 2)]); - assert_eq!(ledger.total, 425); + assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 250 / 2)]); + assert_eq!(ledger.total, 900 / 2 - dust); assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); assert_eq!( LedgerSlashPerEra::get().1, @@ -4890,7 +4920,6 @@ fn ledger_slash_works() { ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; ledger.active = 500; ledger.total = 40 + 10 + 100 + 250 + 500; // 900 - // When assert_eq!( ledger.slash( 900 / 2, @@ -4902,7 +4931,7 @@ fn ledger_slash_works() { ); // Then assert_eq!(ledger.active, 500 / 2); - assert_eq!(ledger.unlocking, vec![c(4, 0), c(5, 100 / 2), c(6, 0), c(7, 250 / 2)]); + assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 250 / 2)]); assert_eq!(ledger.total, 425); assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); assert_eq!( @@ -4911,10 +4940,10 @@ fn ledger_slash_works() { ); // Given + // slash order --------------------NA--------2----------0----------1---- ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; ledger.active = 500; ledger.total = 40 + 10 + 100 + 250 + 500; // 900 - // When assert_eq!( ledger.slash( 500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2 @@ -4927,13 +4956,13 @@ fn ledger_slash_works() { ); // Then assert_eq!(ledger.active, 0); - // iteration order ------------------NA----------2-------------0--------1---- - assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2), c(6, 0), c(7, 0)]); + assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2)]); assert_eq!(ledger.total, 90); assert_eq!(LedgerSlashPerEra::get().0, 0); assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)])); // Given + // iteration order------------------NA---------2----------0----------1---- ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; ledger.active = 100; ledger.total = 5 * 100; @@ -4950,8 +4979,7 @@ fn ledger_slash_works() { ); // Then assert_eq!(ledger.active, 0); - // iteration order ------------------NA---------2--------0--------1---- - assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 0), c(6, 0), c(7, 0)]); + assert_eq!(ledger.unlocking, vec![c(4, 100)]); //------goes to min balance and then gets dusted^^^ assert_eq!(ledger.total, 100); assert_eq!(LedgerSlashPerEra::get().0, 0); diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 09eff0717c8e8..57d858be1e0ef 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -37,12 +37,12 @@ pub trait OnStakerSlash { /// # Arguments /// /// * `stash` - The stash of the staker whom the slash was applied to. - /// * `slashed_bonded` - The new bonded balance of the staker after the slash was applied. - /// * `slashed_unlocking` - A map from eras that the staker is unbonding in to the new balance - /// after the slash was applied. + /// * `slashed_active` - The new bonded balance of the staker after the slash was applied. + /// * `slashed_unlocking` - a map of slashed eras, and the balance of that unlocking chunk after + /// the slash is applied. Any era not present in the map is not affected at all. fn on_slash( stash: &AccountId, - slashed_bonded: Balance, + slashed_active: Balance, slashed_unlocking: &BTreeMap, ); } From d739887a709ee03cada4b671c76050c57c7d08c2 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 16 Mar 2022 19:39:45 +0000 Subject: [PATCH 192/299] Add TODO for ED QoL at reward pool creation --- frame/nomination-pools/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 0b4fa9242c157..2b948ba6a0cba 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1366,6 +1366,11 @@ pub mod pallet { /// * `root` - The account to set as [`BondedPool::root`]. /// * `nominator` - The account to set as the [`BondedPool::nominator`]. /// * `state_toggler` - The account to set as the [`BondedPool::state_toggler`]. + // TODO: The creator needs to transfer ED to the pool account and then have their delegators + // `reward_pool_total_earnings` ever set to the balance of the reward pool. This will make + // an invariant that the reward pool account will always have ED until destroyed. + // The reward pool balance and total earnings ever will also need to be updated to reflect + // that it has ED so the payout calculations work #[pallet::weight(T::WeightInfo::create())] #[frame_support::transactional] pub fn create( @@ -1391,6 +1396,7 @@ pub mod pallet { ); ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); + // TODO: transfer ED to reward pool and update reward pool total earnings and balance. let pool_id = LastPoolId::::mutate(|id| { *id += 1; *id From 850cb3e80aa6462830bec7b545a434d0528ae774 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 16 Mar 2022 21:14:13 +0000 Subject: [PATCH 193/299] Make sure reward pool never gets dusted --- frame/nomination-pools/src/lib.rs | 49 +++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 2b948ba6a0cba..216e914cf97d5 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1036,7 +1036,8 @@ pub mod pallet { DoesNotHavePermission, /// Metadata exceeds [`T::MaxMetadataLen`] MetadataExceedsMaxLen, - /// Some error occurred that should never happen. This should be reported to the maintainers. + /// Some error occurred that should never happen. This should be reported to the + /// maintainers. DefensiveError, } @@ -1327,9 +1328,9 @@ pub mod pallet { // Kill accounts from storage by making their balance go below ED. We assume that // the accounts have no references that would prevent destruction once we get to // this point. - debug_assert_eq!( - T::Currency::free_balance(&bonded_pool.reward_account()), - Zero::zero() + debug_assert!( + T::Currency::free_balance(&bonded_pool.reward_account()) >= + T::Currency::minimum_balance() ); debug_assert_eq!( T::Currency::free_balance(&bonded_pool.bonded_account()), @@ -1366,6 +1367,12 @@ pub mod pallet { /// * `root` - The account to set as [`BondedPool::root`]. /// * `nominator` - The account to set as the [`BondedPool::nominator`]. /// * `state_toggler` - The account to set as the [`BondedPool::state_toggler`]. + /// + /// # Notes + /// + /// The caller will transfer `amount` to the bonded account and existential deposit to the + /// reward account. While the former is returned when the caller withdraws unbonded funds, + /// the latter is not guaranteed to be returned. // TODO: The creator needs to transfer ED to the pool account and then have their delegators // `reward_pool_total_earnings` ever set to the balance of the reward pool. This will make // an invariant that the reward pool account will always have ED until destroyed. @@ -1395,8 +1402,12 @@ pub mod pallet { Error::::MaxPools ); ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); + ensure!( + T::Currency::free_balance(&who) >= + amount.saturating_add(T::Currency::minimum_balance()), + Error::::MinimumBondNotMet + ); - // TODO: transfer ED to reward pool and update reward pool total earnings and balance. let pool_id = LastPoolId::::mutate(|id| { *id += 1; *id @@ -1408,6 +1419,25 @@ pub mod pallet { let points = bonded_pool.try_bond_delegator(&who, amount, PoolBond::Create)?; + // The depositor transfers ED to the pool account and then have their delegator + // `reward_pool_total_earnings` ever set to the balance of the reward pool. This will + // ensure the invariant that the reward pool account will not be dusted until the pool + // is destroyed. + T::Currency::transfer( + &who, + &bonded_pool.reward_account(), + T::Currency::minimum_balance(), + ExistenceRequirement::AllowDeath, + )?; + let mut reward_pool = RewardPool:: { + balance: Zero::zero(), + points: U256::zero(), + total_earnings: Zero::zero(), + }; + // Make sure the reward pool has correct balance and earnings so the first payout claim + // does not wipe the ED. This must be done after the transfer + reward_pool.update_total_earnings_and_balance(bonded_pool.id); + Delegators::::insert( who.clone(), Delegator:: { @@ -1417,14 +1447,7 @@ pub mod pallet { unbonding_era: None, }, ); - RewardPools::::insert( - pool_id, - RewardPool:: { - balance: Zero::zero(), - points: U256::zero(), - total_earnings: Zero::zero(), - }, - ); + RewardPools::::insert(pool_id, reward_pool); ReversePoolIdLookup::::insert(bonded_pool.bonded_account(), pool_id); Self::deposit_event(Event::::Created { depositor: who, pool_id }); bonded_pool.put(); From f48cd379e608f98c70bd64a83fbc2e4530b115e1 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 16 Mar 2022 21:16:49 +0000 Subject: [PATCH 194/299] Improve error type --- frame/nomination-pools/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 216e914cf97d5..e2030b2f1dd38 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1004,6 +1004,8 @@ pub mod pallet { AccountBelongsToOtherPool, /// The pool has insufficient balance to bond as a nominator. InsufficientBond, + /// The caller does not have enough funds to create pool. + InsufficientFundsToCreate, /// The delegator is already unbonding. AlreadyUnbonding, /// The delegator is not unbonding and thus cannot withdraw funds. @@ -1405,7 +1407,7 @@ pub mod pallet { ensure!( T::Currency::free_balance(&who) >= amount.saturating_add(T::Currency::minimum_balance()), - Error::::MinimumBondNotMet + Error::::InsufficientFundsToCreate ); let pool_id = LastPoolId::::mutate(|id| { From 9ee82684aa991da8701aaa64ada2f9a5288d8b2b Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 16 Mar 2022 22:10:38 +0000 Subject: [PATCH 195/299] demonstrate per_thing usage --- frame/staking/src/lib.rs | 22 ++++++++-------------- frame/staking/src/mock.rs | 1 + frame/staking/src/pallet/mod.rs | 18 ++++++++++++++++-- frame/staking/src/tests.rs | 26 +++++++++----------------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index af7a059f66a4c..9765f9705637a 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -310,7 +310,7 @@ use scale_info::TypeInfo; use sp_runtime::{ curve::PiecewiseLinear, traits::{AtLeast32BitUnsigned, Convert, Saturating, Zero}, - Perbill, RuntimeDebug, + Perbill, Perquintill, RuntimeDebug, }; use sp_staking::{ offence::{Offence, OffenceError, ReportOffence}, @@ -338,8 +338,7 @@ macro_rules! log { pub type RewardPoint = u32; /// The balance type of this pallet. -pub type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; +pub type BalanceOf = ::CurrencyBalance; type PositiveImbalanceOf = <::Currency as Currency< ::AccountId, @@ -560,9 +559,7 @@ impl StakingLedger { minimum_balance: BalanceOf, slash_era: EraIndex, ) -> BalanceOf { - use sp_runtime::traits::CheckedMul as _; use sp_staking::OnStakerSlash as _; - use sp_std::ops::Div as _; if slash_amount.is_zero() { return Zero::zero() @@ -601,21 +598,18 @@ impl StakingLedger { } }; - let is_proportional_slash = slash_amount < affected_balance; // Helper to update `target` and the ledgers total after accounting for slashing `target`. // TODO: use PerThing let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { - let maybe_numerator = slash_amount.checked_mul(target); - let mut slash_from_target = match (maybe_numerator, is_proportional_slash) { - // Equivalent to `(slash_amount / affected_balance) * target`. - (Some(numerator), true) => numerator.div(affected_balance), - // If the slash amount is gt than the affected balance OR the arithmetic to - // calculate the proportion saturated, we just try to slash as much as possible. - (None, _) | (_, false) => *slash_remaining, + let mut slash_from_target = if slash_amount < affected_balance { + let ratio = Perquintill::from_rational(slash_amount, affected_balance); + ratio * (*target) + } else { + *slash_remaining } .min(*target); - // finally, slash out from *target exactly `slash_from_target`. + // slash out from *target exactly `slash_from_target`. *target = *target - slash_from_target; if *target <= minimum_balance { // Slash the rest of the target if its dust diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index d4bc383a29499..0ab40cfe019b3 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -265,6 +265,7 @@ impl sp_staking::OnStakerSlash for OnStakerSlashM impl crate::pallet::pallet::Config for Test { type MaxNominations = MaxNominations; type Currency = Balances; + type CurrencyBalance = ::Balance; type UnixTime = Timestamp; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type RewardRemainder = RewardRemainderMock; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 775cf95d9ab18..a467a3e112cdc 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -75,8 +75,22 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config + SendTransactionTypes> { /// The staking balance. - type Currency: LockableCurrency; - + type Currency: LockableCurrency< + Self::AccountId, + Moment = Self::BlockNumber, + Balance = Self::CurrencyBalance, + >; + /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to + /// `From`. + type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned + + codec::FullCodec + + Copy + + MaybeSerializeDeserialize + + sp_std::fmt::Debug + + Default + + From + + TypeInfo + + MaxEncodedLen; /// Time used for computing era duration. /// /// It is guaranteed to start being called from the first `on_finalize`. Thus value at diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 624c6c9003799..90931e50eda93 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -2104,7 +2104,7 @@ fn reward_validator_slashing_validator_does_not_overflow() { &[Perbill::from_percent(100)], ); - assert_eq!(Balances::total_balance(&11), stake - 1); + assert_eq!(Balances::total_balance(&11), stake); assert_eq!(Balances::total_balance(&2), 1); }) } @@ -4992,27 +4992,19 @@ fn ledger_slash_works() { let value = slash - (9 * 4) // The value of the other parts of ledger that will get slashed + 1; - // slash * value will saturate - assert!(slash.checked_mul(value - 20).is_none()); + ledger.active = 10; ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)]; ledger.total = value + 40; // When - assert_eq!(ledger.slash(slash, 0, 0), slash); + let slash_amount = ledger.slash(slash, 0, 0); + assert_eq_error_rate!(slash_amount, slash, 5); // Then - assert_eq!(ledger.active, 1); // slash of 9 - assert_eq!( - ledger.unlocking, - vec![ - c(4, 1), // slash of 9 - c(5, 1), // slash of 9 - c(6, 1), // slash of 9 - c(7, 1), // saturates, so slash of remaining_slash.min(value) equivalent of (slash - 9 * 4).min(value) - ] - ); - assert_eq!(ledger.total, 5); - assert_eq!(LedgerSlashPerEra::get().0, 1); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 1), (5, 1), (6, 1), (7, 1)])); + assert_eq!(ledger.active, 0); // slash of 9 + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0), (6, 0), (7, 0)])); // Given let slash = u64::MAX as Balance * 2; From cdc2a32d22e6c00609bf496a5854b8f99f031156 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 17 Mar 2022 13:12:27 +0000 Subject: [PATCH 196/299] Update sanity check & fix create_works --- frame/nomination-pools/src/lib.rs | 11 ++++++++--- frame/nomination-pools/src/tests.rs | 16 +++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index e2030b2f1dd38..33ffeffa40503 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1430,14 +1430,15 @@ pub mod pallet { &bonded_pool.reward_account(), T::Currency::minimum_balance(), ExistenceRequirement::AllowDeath, - )?; + ) + .defensive_map_err(|e| e)?; let mut reward_pool = RewardPool:: { balance: Zero::zero(), points: U256::zero(), total_earnings: Zero::zero(), }; // Make sure the reward pool has correct balance and earnings so the first payout claim - // does not wipe the ED. This must be done after the transfer + // does not wipe the ED. This must be done after transferring to the reward account. reward_pool.update_total_earnings_and_balance(bonded_pool.id); Delegators::::insert( @@ -1445,7 +1446,7 @@ pub mod pallet { Delegator:: { pool_id, points, - reward_pool_total_earnings: Zero::zero(), + reward_pool_total_earnings: reward_pool.total_earnings, unbonding_era: None, }, ); @@ -1795,6 +1796,7 @@ impl Pallet { let mut all_delegators = 0u32; Delegators::::iter().for_each(|(_, d)| { assert!(BondedPools::::contains_key(d.pool_id)); + assert!(d.reward_pool_total_earnings >= T::Currency::minimum_balance()); *pools_delegators.entry(d.pool_id).or_default() += 1; all_delegators += 1; }); @@ -1835,6 +1837,9 @@ impl Pallet { bonded_balance, sum_unbonding_balance ); + + let reward_account = Pallet::::create_reward_account(pool_id); + assert!(T::Currency::free_balance(&reward_account) >= T::Currency::minimum_balance()); } Ok(()) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 8b33de7da674c..b5dafe168ee51 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -2098,7 +2098,8 @@ mod create { assert!(!Delegators::::contains_key(11)); assert_eq!(StakingMock::active_stake(&next_pool_stash), None); - Balances::make_free_balance_be(&11, StakingMock::minimum_bond()); + let min_create_free = StakingMock::minimum_bond() + Balances::minimum_balance(); + Balances::make_free_balance_be(&11, min_create_free); assert_ok!(Pools::create( Origin::signed(11), StakingMock::minimum_bond(), @@ -2113,12 +2114,13 @@ mod create { Delegator { pool_id: 2, points: StakingMock::minimum_bond(), - reward_pool_total_earnings: Zero::zero(), + reward_pool_total_earnings: Balances::minimum_balance(), unbonding_era: None } ); + let bonded_pool = BondedPool::::get(2).unwrap(); assert_eq!( - BondedPool::::get(2).unwrap(), + bonded_pool, BondedPool { id: 2, inner: BondedPoolInner { @@ -2141,11 +2143,15 @@ mod create { assert_eq!( RewardPools::::get(2).unwrap(), RewardPool { - balance: Zero::zero(), + balance: Balances::minimum_balance(), points: U256::zero(), - total_earnings: Zero::zero(), + total_earnings: Balances::minimum_balance(), } ); + assert_eq!( + Balances::free_balance(bonded_pool.reward_account()), + Balances::minimum_balance() + ); }); } From b410010ec4c9bc05410b62ebbba2a39d68d18c99 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 17 Mar 2022 13:38:09 +0000 Subject: [PATCH 197/299] Improve test ext pool creation & fix some more tests --- frame/nomination-pools/src/mock.rs | 2 +- frame/nomination-pools/src/tests.rs | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 08ed41e58369b..6aa3227585ce3 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -221,7 +221,7 @@ impl ExtBuilder { ext.execute_with(|| { // make a pool let amount_to_bond = ::StakingInterface::minimum_bond(); - Balances::make_free_balance_be(&10, amount_to_bond * 2); + Balances::make_free_balance_be(&10, amount_to_bond * 2 + Balances::minimum_balance()); assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902)); let last_pool = LastPoolId::::get(); diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index b5dafe168ee51..84f3277218a40 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -37,14 +37,18 @@ fn test_setup_works() { ); assert_eq!( RewardPools::::get(last_pool).unwrap(), - RewardPool:: { balance: 0, points: 0.into(), total_earnings: 0 } + RewardPool:: { + balance: Balances::minimum_balance(), + points: 0.into(), + total_earnings: Balances::minimum_balance() + } ); assert_eq!( Delegators::::get(10).unwrap(), Delegator:: { pool_id: last_pool, points: 10, - reward_pool_total_earnings: 0, + reward_pool_total_earnings: Balances::minimum_balance(), unbonding_era: None } ) @@ -374,7 +378,7 @@ mod join { Delegator:: { pool_id: 1, points: 2, - reward_pool_total_earnings: 0, + reward_pool_total_earnings: Balances::minimum_balance(), unbonding_era: None } ); @@ -397,7 +401,7 @@ mod join { Delegator:: { pool_id: 1, points: 24, - reward_pool_total_earnings: 0, + reward_pool_total_earnings: Balances::minimum_balance(), unbonding_era: None } ); @@ -1742,6 +1746,7 @@ mod withdraw_unbonded_other { assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); unsafe_set_state(1, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); + assert_eq!(Balances::free_balance(&10), 10); SubPoolsStorage::::insert( 1, @@ -2207,6 +2212,15 @@ mod create { MaxPools::::put(3); MaxDelegators::::put(1); + // Then + assert_noop!( + Pools::create(Origin::signed(11), 20, 11, 11, 11), + Error::::InsufficientFundsToCreate + ); + + // Given + Balances::make_free_balance_be(&11, Balances::minimum_balance() + 20); + // Then assert_noop!( Pools::create(Origin::signed(11), 20, 11, 11, 11), From a6d1c9b920ad995fa8cdcbf6561b8db81c7a7783 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:32:23 +0000 Subject: [PATCH 198/299] Try revert --- frame/nomination-pools/src/lib.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 33ffeffa40503..d0b5c398fd7b6 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1369,17 +1369,6 @@ pub mod pallet { /// * `root` - The account to set as [`BondedPool::root`]. /// * `nominator` - The account to set as the [`BondedPool::nominator`]. /// * `state_toggler` - The account to set as the [`BondedPool::state_toggler`]. - /// - /// # Notes - /// - /// The caller will transfer `amount` to the bonded account and existential deposit to the - /// reward account. While the former is returned when the caller withdraws unbonded funds, - /// the latter is not guaranteed to be returned. - // TODO: The creator needs to transfer ED to the pool account and then have their delegators - // `reward_pool_total_earnings` ever set to the balance of the reward pool. This will make - // an invariant that the reward pool account will always have ED until destroyed. - // The reward pool balance and total earnings ever will also need to be updated to reflect - // that it has ED so the payout calculations work #[pallet::weight(T::WeightInfo::create())] #[frame_support::transactional] pub fn create( From f3024c1707db7541bdbbc8ae52f32017d90d07a1 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:40:14 +0000 Subject: [PATCH 199/299] Revert "Try revert" This reverts commit a6d1c9b920ad995fa8cdcbf6561b8db81c7a7783. --- frame/nomination-pools/src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index d0b5c398fd7b6..33ffeffa40503 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1369,6 +1369,17 @@ pub mod pallet { /// * `root` - The account to set as [`BondedPool::root`]. /// * `nominator` - The account to set as the [`BondedPool::nominator`]. /// * `state_toggler` - The account to set as the [`BondedPool::state_toggler`]. + /// + /// # Notes + /// + /// The caller will transfer `amount` to the bonded account and existential deposit to the + /// reward account. While the former is returned when the caller withdraws unbonded funds, + /// the latter is not guaranteed to be returned. + // TODO: The creator needs to transfer ED to the pool account and then have their delegators + // `reward_pool_total_earnings` ever set to the balance of the reward pool. This will make + // an invariant that the reward pool account will always have ED until destroyed. + // The reward pool balance and total earnings ever will also need to be updated to reflect + // that it has ED so the payout calculations work #[pallet::weight(T::WeightInfo::create())] #[frame_support::transactional] pub fn create( From 074ecd0042256798cf1c14d03c464e378af39d54 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:40:20 +0000 Subject: [PATCH 200/299] Revert "Improve test ext pool creation & fix some more tests" This reverts commit b410010ec4c9bc05410b62ebbba2a39d68d18c99. --- frame/nomination-pools/src/mock.rs | 2 +- frame/nomination-pools/src/tests.rs | 22 ++++------------------ 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 6aa3227585ce3..08ed41e58369b 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -221,7 +221,7 @@ impl ExtBuilder { ext.execute_with(|| { // make a pool let amount_to_bond = ::StakingInterface::minimum_bond(); - Balances::make_free_balance_be(&10, amount_to_bond * 2 + Balances::minimum_balance()); + Balances::make_free_balance_be(&10, amount_to_bond * 2); assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902)); let last_pool = LastPoolId::::get(); diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 84f3277218a40..b5dafe168ee51 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -37,18 +37,14 @@ fn test_setup_works() { ); assert_eq!( RewardPools::::get(last_pool).unwrap(), - RewardPool:: { - balance: Balances::minimum_balance(), - points: 0.into(), - total_earnings: Balances::minimum_balance() - } + RewardPool:: { balance: 0, points: 0.into(), total_earnings: 0 } ); assert_eq!( Delegators::::get(10).unwrap(), Delegator:: { pool_id: last_pool, points: 10, - reward_pool_total_earnings: Balances::minimum_balance(), + reward_pool_total_earnings: 0, unbonding_era: None } ) @@ -378,7 +374,7 @@ mod join { Delegator:: { pool_id: 1, points: 2, - reward_pool_total_earnings: Balances::minimum_balance(), + reward_pool_total_earnings: 0, unbonding_era: None } ); @@ -401,7 +397,7 @@ mod join { Delegator:: { pool_id: 1, points: 24, - reward_pool_total_earnings: Balances::minimum_balance(), + reward_pool_total_earnings: 0, unbonding_era: None } ); @@ -1746,7 +1742,6 @@ mod withdraw_unbonded_other { assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); unsafe_set_state(1, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); - assert_eq!(Balances::free_balance(&10), 10); SubPoolsStorage::::insert( 1, @@ -2212,15 +2207,6 @@ mod create { MaxPools::::put(3); MaxDelegators::::put(1); - // Then - assert_noop!( - Pools::create(Origin::signed(11), 20, 11, 11, 11), - Error::::InsufficientFundsToCreate - ); - - // Given - Balances::make_free_balance_be(&11, Balances::minimum_balance() + 20); - // Then assert_noop!( Pools::create(Origin::signed(11), 20, 11, 11, 11), From f7fb1661f2c986a2de1f4ba62a73cacfbd16a3d7 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:40:30 +0000 Subject: [PATCH 201/299] Revert "Update sanity check & fix create_works" This reverts commit cdc2a32d22e6c00609bf496a5854b8f99f031156. Roll back reward account funding --- frame/nomination-pools/src/lib.rs | 11 +++-------- frame/nomination-pools/src/tests.rs | 16 +++++----------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 33ffeffa40503..e2030b2f1dd38 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1430,15 +1430,14 @@ pub mod pallet { &bonded_pool.reward_account(), T::Currency::minimum_balance(), ExistenceRequirement::AllowDeath, - ) - .defensive_map_err(|e| e)?; + )?; let mut reward_pool = RewardPool:: { balance: Zero::zero(), points: U256::zero(), total_earnings: Zero::zero(), }; // Make sure the reward pool has correct balance and earnings so the first payout claim - // does not wipe the ED. This must be done after transferring to the reward account. + // does not wipe the ED. This must be done after the transfer reward_pool.update_total_earnings_and_balance(bonded_pool.id); Delegators::::insert( @@ -1446,7 +1445,7 @@ pub mod pallet { Delegator:: { pool_id, points, - reward_pool_total_earnings: reward_pool.total_earnings, + reward_pool_total_earnings: Zero::zero(), unbonding_era: None, }, ); @@ -1796,7 +1795,6 @@ impl Pallet { let mut all_delegators = 0u32; Delegators::::iter().for_each(|(_, d)| { assert!(BondedPools::::contains_key(d.pool_id)); - assert!(d.reward_pool_total_earnings >= T::Currency::minimum_balance()); *pools_delegators.entry(d.pool_id).or_default() += 1; all_delegators += 1; }); @@ -1837,9 +1835,6 @@ impl Pallet { bonded_balance, sum_unbonding_balance ); - - let reward_account = Pallet::::create_reward_account(pool_id); - assert!(T::Currency::free_balance(&reward_account) >= T::Currency::minimum_balance()); } Ok(()) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index b5dafe168ee51..8b33de7da674c 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -2098,8 +2098,7 @@ mod create { assert!(!Delegators::::contains_key(11)); assert_eq!(StakingMock::active_stake(&next_pool_stash), None); - let min_create_free = StakingMock::minimum_bond() + Balances::minimum_balance(); - Balances::make_free_balance_be(&11, min_create_free); + Balances::make_free_balance_be(&11, StakingMock::minimum_bond()); assert_ok!(Pools::create( Origin::signed(11), StakingMock::minimum_bond(), @@ -2114,13 +2113,12 @@ mod create { Delegator { pool_id: 2, points: StakingMock::minimum_bond(), - reward_pool_total_earnings: Balances::minimum_balance(), + reward_pool_total_earnings: Zero::zero(), unbonding_era: None } ); - let bonded_pool = BondedPool::::get(2).unwrap(); assert_eq!( - bonded_pool, + BondedPool::::get(2).unwrap(), BondedPool { id: 2, inner: BondedPoolInner { @@ -2143,15 +2141,11 @@ mod create { assert_eq!( RewardPools::::get(2).unwrap(), RewardPool { - balance: Balances::minimum_balance(), + balance: Zero::zero(), points: U256::zero(), - total_earnings: Balances::minimum_balance(), + total_earnings: Zero::zero(), } ); - assert_eq!( - Balances::free_balance(bonded_pool.reward_account()), - Balances::minimum_balance() - ); }); } From b994b64986215685ad2a120a532dd9936718e2cb Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:40:55 +0000 Subject: [PATCH 202/299] Revert "Improve error type" This reverts commit f48cd379e608f98c70bd64a83fbc2e4530b115e1. --- frame/nomination-pools/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index e2030b2f1dd38..216e914cf97d5 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1004,8 +1004,6 @@ pub mod pallet { AccountBelongsToOtherPool, /// The pool has insufficient balance to bond as a nominator. InsufficientBond, - /// The caller does not have enough funds to create pool. - InsufficientFundsToCreate, /// The delegator is already unbonding. AlreadyUnbonding, /// The delegator is not unbonding and thus cannot withdraw funds. @@ -1407,7 +1405,7 @@ pub mod pallet { ensure!( T::Currency::free_balance(&who) >= amount.saturating_add(T::Currency::minimum_balance()), - Error::::InsufficientFundsToCreate + Error::::MinimumBondNotMet ); let pool_id = LastPoolId::::mutate(|id| { From e42fce6728c91f5d0d03b36fe7979ef444706123 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:40:59 +0000 Subject: [PATCH 203/299] Revert "Make sure reward pool never gets dusted" This reverts commit 850cb3e80aa6462830bec7b545a434d0528ae774. revert --- frame/nomination-pools/src/lib.rs | 49 ++++++++----------------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 216e914cf97d5..2b948ba6a0cba 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1036,8 +1036,7 @@ pub mod pallet { DoesNotHavePermission, /// Metadata exceeds [`T::MaxMetadataLen`] MetadataExceedsMaxLen, - /// Some error occurred that should never happen. This should be reported to the - /// maintainers. + /// Some error occurred that should never happen. This should be reported to the maintainers. DefensiveError, } @@ -1328,9 +1327,9 @@ pub mod pallet { // Kill accounts from storage by making their balance go below ED. We assume that // the accounts have no references that would prevent destruction once we get to // this point. - debug_assert!( - T::Currency::free_balance(&bonded_pool.reward_account()) >= - T::Currency::minimum_balance() + debug_assert_eq!( + T::Currency::free_balance(&bonded_pool.reward_account()), + Zero::zero() ); debug_assert_eq!( T::Currency::free_balance(&bonded_pool.bonded_account()), @@ -1367,12 +1366,6 @@ pub mod pallet { /// * `root` - The account to set as [`BondedPool::root`]. /// * `nominator` - The account to set as the [`BondedPool::nominator`]. /// * `state_toggler` - The account to set as the [`BondedPool::state_toggler`]. - /// - /// # Notes - /// - /// The caller will transfer `amount` to the bonded account and existential deposit to the - /// reward account. While the former is returned when the caller withdraws unbonded funds, - /// the latter is not guaranteed to be returned. // TODO: The creator needs to transfer ED to the pool account and then have their delegators // `reward_pool_total_earnings` ever set to the balance of the reward pool. This will make // an invariant that the reward pool account will always have ED until destroyed. @@ -1402,12 +1395,8 @@ pub mod pallet { Error::::MaxPools ); ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); - ensure!( - T::Currency::free_balance(&who) >= - amount.saturating_add(T::Currency::minimum_balance()), - Error::::MinimumBondNotMet - ); + // TODO: transfer ED to reward pool and update reward pool total earnings and balance. let pool_id = LastPoolId::::mutate(|id| { *id += 1; *id @@ -1419,25 +1408,6 @@ pub mod pallet { let points = bonded_pool.try_bond_delegator(&who, amount, PoolBond::Create)?; - // The depositor transfers ED to the pool account and then have their delegator - // `reward_pool_total_earnings` ever set to the balance of the reward pool. This will - // ensure the invariant that the reward pool account will not be dusted until the pool - // is destroyed. - T::Currency::transfer( - &who, - &bonded_pool.reward_account(), - T::Currency::minimum_balance(), - ExistenceRequirement::AllowDeath, - )?; - let mut reward_pool = RewardPool:: { - balance: Zero::zero(), - points: U256::zero(), - total_earnings: Zero::zero(), - }; - // Make sure the reward pool has correct balance and earnings so the first payout claim - // does not wipe the ED. This must be done after the transfer - reward_pool.update_total_earnings_and_balance(bonded_pool.id); - Delegators::::insert( who.clone(), Delegator:: { @@ -1447,7 +1417,14 @@ pub mod pallet { unbonding_era: None, }, ); - RewardPools::::insert(pool_id, reward_pool); + RewardPools::::insert( + pool_id, + RewardPool:: { + balance: Zero::zero(), + points: U256::zero(), + total_earnings: Zero::zero(), + }, + ); ReversePoolIdLookup::::insert(bonded_pool.bonded_account(), pool_id); Self::deposit_event(Event::::Created { depositor: who, pool_id }); bonded_pool.put(); From 6c62233d9f9b17adf33511b8c3cb7a9711a7fef8 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 21 Mar 2022 13:31:25 +0100 Subject: [PATCH 204/299] Update some tests --- frame/staking/src/lib.rs | 4 +-- frame/staking/src/tests.rs | 54 ++++++++++---------------------------- 2 files changed, 15 insertions(+), 43 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 9765f9705637a..4e3f163c29687 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -599,10 +599,9 @@ impl StakingLedger { }; // Helper to update `target` and the ledgers total after accounting for slashing `target`. - // TODO: use PerThing + let ratio = Perquintill::from_rational(slash_amount, affected_balance); let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { let mut slash_from_target = if slash_amount < affected_balance { - let ratio = Perquintill::from_rational(slash_amount, affected_balance); ratio * (*target) } else { *slash_remaining @@ -617,7 +616,6 @@ impl StakingLedger { sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) } - // TODO: maybe move this outside of the closure to keep it a bit more pure. self.total = self.total.saturating_sub(slash_from_target); *slash_remaining = slash_remaining.saturating_sub(slash_from_target); }; diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 90931e50eda93..6538115091b1c 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4875,22 +4875,6 @@ fn ledger_slash_works() { assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4); assert_eq!(LedgerSlashPerEra::get().1, Default::default()); - // // slash 1/4th with chunks - // ledger.unlocking = bounded_vec![c(4, 40), c(5, 120), c(6, 80)]; - // ledger.active = 120; - // ledger.total = 40 + 120 + 80 + 120; // 240 - // // When we have a partial slash that touches all chunks - // assert_eq!(ledger.slash(240 / 4, 0, 0), 2 / 4 - 1); - // // Then - // assert_eq!(ledger.unlocking, vec![c(4, 3 * 40 / 4), c(5, 3 * 120 / 4), c(6, 3 * 80 / 4)]); - // assert_eq!(ledger.active, 3 * 120 / 4); - // assert_eq!(ledger.total, 3 * 240 / 4 + 1); - // assert_eq!(LedgerSlashPerEra::get().0, 3 * 240 / 4); - // assert_eq!( - // LedgerSlashPerEra::get().1, - // BTreeMap::from([(4, 3 * 40 / 4), (5, 3 * 80 / 4), (6, 3 * 120 / 4)]) - // ); - // Given we have the same as above, ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; ledger.active = 500; @@ -5019,36 +5003,26 @@ fn ledger_slash_works() { ledger.active = unit; ledger.total = unit * 4 + value; // When - assert_eq!(ledger.slash(slash, 0, 0), slash); + assert_eq!(ledger.slash(slash, 0, 0), slash - 43); // Then // The amount slashed out of `unit` - let unit_slash = { - let affected_balance = value + unit * 4; - slash * unit / affected_balance - }; + let affected_balance = value + unit * 4; + let ratio = Perquintill::from_rational(slash, affected_balance); // `unit` after the slash is applied - let unit_slashed = unit - unit_slash; - // `value` after the slash is applied + let unit_slashed = { + let unit_slash = ratio * unit; + unit - unit_slash + }; let value_slashed = { - // We slash active and era 4 before we slash era 5 - let previous_slashed_amount = unit_slash * 2; - let remaining_slash = slash - previous_slashed_amount; - value - remaining_slash + let value_slash = ratio * value; + value - value_slash }; assert_eq!(ledger.active, unit_slashed); + assert_eq!(ledger.unlocking, vec![c(5, value_slashed)]); + assert_eq!(ledger.total, value_slashed); + assert_eq!(LedgerSlashPerEra::get().0, 0); assert_eq!( - ledger.unlocking, - vec![ - c(4, unit_slashed), - // We reached the full slash amount here because we slashed value.min(remaining_slash), - // which was remaining_slash - c(5, value_slashed), - c(6, unit), /* The rest are untouched even though they where in the slashing range. - * This is problematic for pools, but should be rare enough that its ok. */ - c(7, unit) - ] + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 0)]) ); - assert_eq!(ledger.total, unit_slashed + unit_slashed + value_slashed + unit + unit); - assert_eq!(LedgerSlashPerEra::get().0, unit_slashed); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, unit_slashed), (5, value_slashed)])); } From 35a3077caa03eae3309e4fe1e1985772e3701d64 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 21 Mar 2022 13:40:12 +0100 Subject: [PATCH 205/299] FMT --- frame/staking/src/lib.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 4e3f163c29687..74d973eac5aaf 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -601,12 +601,9 @@ impl StakingLedger { // Helper to update `target` and the ledgers total after accounting for slashing `target`. let ratio = Perquintill::from_rational(slash_amount, affected_balance); let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { - let mut slash_from_target = if slash_amount < affected_balance { - ratio * (*target) - } else { - *slash_remaining - } - .min(*target); + let mut slash_from_target = + if slash_amount < affected_balance { ratio * (*target) } else { *slash_remaining } + .min(*target); // slash out from *target exactly `slash_from_target`. *target = *target - slash_from_target; From 7c3ca1e916c6839fe0f91665d30a6e757f63cfaf Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 21 Mar 2022 21:49:14 +0100 Subject: [PATCH 206/299] Test that era offset works correctly --- frame/staking/src/lib.rs | 2 -- frame/staking/src/tests.rs | 13 +++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 74d973eac5aaf..ffb4030792824 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -569,8 +569,6 @@ impl StakingLedger { let pre_slash_total = self.total; let era_after_slash = slash_era + 1; - // TODO: test to make sure chunks before the slash are never slashed. - // at this era onwards, the funds can be slashed. let chunk_unlock_era_after_slash = era_after_slash + T::BondingDuration::get(); // Calculate the total balance of active funds and unlocking funds in the affected range. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index bf023c3183084..9cdeed4a2fc41 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4850,6 +4850,19 @@ fn ledger_slash_works() { assert_eq!(LedgerSlashPerEra::get().0, 0); assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)])); + // TODO test does not slash earlier eras + // Given + ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; + ledger.total = 4 * 100; + ledger.active = 0; + // When the first 2 chunks don't overlap with the affected range of unlock eras. + assert_eq!(ledger.slash(140, 0, 2), 140); // TODO + // Then + assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]); + assert_eq!(ledger.total, 4 * 100 - 140); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 30), (7, 30)])); + // Given ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; ledger.active = 500; From 4a9e8aa0e22ba641de4d3f70b2a9a15501bdadbd Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 21 Mar 2022 22:49:18 +0100 Subject: [PATCH 207/299] Update mocks --- bin/node/runtime/src/lib.rs | 1 + frame/babe/src/mock.rs | 1 + frame/grandpa/src/mock.rs | 1 + frame/offences/benchmarking/src/mock.rs | 1 + frame/session/benchmarking/src/mock.rs | 1 + frame/staking/src/benchmarking.rs | 4 ++-- 6 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 96a4ebed24c23..fbf13a5c967d9 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -537,6 +537,7 @@ impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig { impl pallet_staking::Config for Runtime { type MaxNominations = MaxNominations; type Currency = Balances; + type CurrencyBalance = Balance; type UnixTime = Timestamp; type CurrencyToVote = U128CurrencyToVote; type RewardRemainder = Treasury; diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 0518b0f3b6644..42f5f9dac8de6 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -183,6 +183,7 @@ impl pallet_staking::Config for Test { type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type Event = Event; type Currency = Balances; + type CurrencyBalance = ::Balance; type Slash = (); type Reward = (); type SessionsPerEra = SessionsPerEra; diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index aef87b66920e5..f2bac7de40b5a 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -191,6 +191,7 @@ impl pallet_staking::Config for Test { type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type Event = Event; type Currency = Balances; + type CurrencyBalance = ::Balance; type Slash = (); type Reward = (); type SessionsPerEra = SessionsPerEra; diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 5937f1cdc29af..e588bce608188 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -158,6 +158,7 @@ impl onchain::Config for Test { impl pallet_staking::Config for Test { type MaxNominations = ConstU32<16>; type Currency = Balances; + type CurrencyBalance = ::Balance; type UnixTime = pallet_timestamp::Pallet; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type RewardRemainder = (); diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index 593b532a303e6..bb39f49602417 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -164,6 +164,7 @@ impl onchain::Config for Test { impl pallet_staking::Config for Test { type MaxNominations = ConstU32<16>; type Currency = Balances; + type CurrencyBalance = ::Balance; type UnixTime = pallet_timestamp::Pallet; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type RewardRemainder = (); diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 7456c16190113..28dae6e657748 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -644,7 +644,7 @@ benchmarks! { assert!(value * l.into() + origin_weight <= dest_weight); let unlock_chunk = UnlockChunk::> { value, - era: EraIndex::zero(), + unlock_era: EraIndex::zero(), }; let stash = scenario.origin_stash1.clone(); @@ -793,7 +793,7 @@ benchmarks! { let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); let unlock_chunk = UnlockChunk::> { value: 1u32.into(), - era: EraIndex::zero(), + unlock_era: EraIndex::zero(), }; for _ in 0 .. l { staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap(); From 7e953dc9505153f3608586af8f0900994ef3d682 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Mar 2022 13:24:34 +0100 Subject: [PATCH 208/299] Remove unnescary docs --- frame/staking/src/lib.rs | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index ffb4030792824..18a8b72a9c70e 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -533,26 +533,10 @@ impl StakingLedger { /// case that either the active bonded or some unlocking chunks become dust after slashing. /// Returns the amount of funds actually slashed. /// - /// Note that this calls `Config::OnStakerSlash::on_slash` with information as to how the slash + /// # Note + /// + /// This calls `Config::OnStakerSlash::on_slash` with information as to how the slash /// was applied. - // Generally slashes are computed by: - // - // 1) Balances of the unlocking chunks in range `slash_era + 1..=apply_era` are summed and - // stored in `total_balance_affected`. - // - // 2) `slash_ratio` is computed as `slash_amount / total_balance_affected`. - // - // 3) `Ledger::active` is set to `(1- slash_ratio) * Ledger::active`. - // - // 4) For all unlocking chunks created in range `slash_era + 1..=apply_era` set their balance - // to `(1 - slash_ratio) * unbonding_pool_balance`. We start with slash_era + 1, because that - // is the earliest the active sake while slash could be unbonded. - // - // 5) Slash any remaining slash amount from the remaining chunks, starting with the `slash_era` - // and going backwards. - // - // There are some edge cases due to saturating arithmetic - see logic below and tests for - // details. fn slash( &mut self, slash_amount: BalanceOf, From 01020d90e149f8b8cba28ac3dcbad79c315471e2 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Mar 2022 18:14:14 +0100 Subject: [PATCH 209/299] Doc updates --- frame/nomination-pools/src/lib.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 2b948ba6a0cba..71e2dc928cb8d 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -73,7 +73,7 @@ //! //! A pool has 3 administrative positions (see [`BondedPool`]): //! -//! * Depositor: creates the pool and is the initial delegator. The can only leave pool once all +//! * Depositor: creates the pool and is the initial delegator. They can only leave pool once all //! other delegators have left. Once they fully leave the pool is destroyed. //! * Nominator: can select which validators the pool nominates. //! * State-Toggler: can change the pools state and kick delegators if the pool is blocked. @@ -265,7 +265,8 @@ //! slashes gives them an incentive to do that if validators get repeatedly slashed. //! //! To be fair to joiners, this implementation also need joining pools, which are actively staking, -//! in addition to the unbonding pools. For maintenance simplicity these are not implemented. Related: https://github.com/paritytech/substrate/issues/10860 +//! in addition to the unbonding pools. For maintenance simplicity these are not implemented. +//! Related: https://github.com/paritytech/substrate/issues/10860 //! //! **Relevant methods:** //! @@ -381,9 +382,15 @@ pub enum PoolState { #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone)] pub struct PoolRoles { + /// Creates the pool and is the initial delegator. They can only leave the pool once all + /// other delegators have left. Once they fully leave, the pool is destroyed. pub depositor: AccountId, + /// Can change the nominator, state-toggler, or itself and can perform any of the actions + /// the nominator or state-toggler can. pub root: AccountId, + /// Can select which validators the pool nominates. pub nominator: AccountId, + /// can change the pools state and kick delegators if the pool is blocked. pub state_toggler: AccountId, } @@ -398,7 +405,7 @@ pub struct BondedPoolInner { pub state: PoolState, /// See [`BondedPool::delegator_counter`] pub delegator_counter: u32, - /// See [`BondedPool::depositor`]. + /// See [`PoolRoles`]. pub roles: PoolRoles, } @@ -1363,14 +1370,9 @@ pub mod pallet { /// destroyed. /// * `index` - A disambiguation index for creating the account. Likely only useful when /// creating multiple pools in the same extrinsic. - /// * `root` - The account to set as [`BondedPool::root`]. - /// * `nominator` - The account to set as the [`BondedPool::nominator`]. - /// * `state_toggler` - The account to set as the [`BondedPool::state_toggler`]. - // TODO: The creator needs to transfer ED to the pool account and then have their delegators - // `reward_pool_total_earnings` ever set to the balance of the reward pool. This will make - // an invariant that the reward pool account will always have ED until destroyed. - // The reward pool balance and total earnings ever will also need to be updated to reflect - // that it has ED so the payout calculations work + /// * `root` - The account to set as [`PoolRoles::root`]. + /// * `nominator` - The account to set as the [`PoolRoles::nominator`]. + /// * `state_toggler` - The account to set as the [`PoolRoles::state_toggler`]. #[pallet::weight(T::WeightInfo::create())] #[frame_support::transactional] pub fn create( From 8c2c214aa8d9509892768585d641430a01641fae Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Mar 2022 19:44:12 +0100 Subject: [PATCH 210/299] Update calculate_delegator_payout_works_with_a_pool_of_1 --- frame/nomination-pools/src/lib.rs | 34 +++++++++++++++++++++--- frame/nomination-pools/src/tests.rs | 40 ++++++++++++++++++++++++----- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 71e2dc928cb8d..42d2a1ecbda79 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -747,7 +747,7 @@ pub struct RewardPool { impl RewardPool { /// Mutate the reward pool by updating the total earnings and current free balance. fn update_total_earnings_and_balance(&mut self, id: PoolId) { - let current_balance = T::Currency::free_balance(&Pallet::::create_reward_account(id)); + let current_balance = Self::current_balance(id); // The earnings since the last time it was updated let new_earnings = current_balance.saturating_sub(self.balance); // The lifetime earnings of the of the reward pool @@ -762,6 +762,13 @@ impl RewardPool { r }) } + + /// The current balance of the reward pool. Never access the reward pools free balance directly. + /// The existential deposit was not received as a reward, so the reward pool can not use it. + fn current_balance(id: PoolId) -> BalanceOf { + T::Currency::free_balance(&Pallet::::create_reward_account(id)) + .saturating_sub(T::Currency::minimum_balance()) + } } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound, RuntimeDebugNoBound)] @@ -1045,6 +1052,8 @@ pub mod pallet { MetadataExceedsMaxLen, /// Some error occurred that should never happen. This should be reported to the maintainers. DefensiveError, + /// The caller has insufficient balance to create the pool. + InsufficientBalanceToCreate, } #[pallet::call] @@ -1397,8 +1406,12 @@ pub mod pallet { Error::::MaxPools ); ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); + ensure!( + T::Currency::free_balance(&who) >= + amount.saturating_add(T::Currency::minimum_balance()), + Error::::InsufficientBalanceToCreate + ); - // TODO: transfer ED to reward pool and update reward pool total earnings and balance. let pool_id = LastPoolId::::mutate(|id| { *id += 1; *id @@ -1619,7 +1632,17 @@ impl Pallet { reward_pool: &mut RewardPool, delegator: &mut Delegator, ) -> Result, DispatchError> { - debug_assert_eq!(delegator.pool_id, bonded_pool.id); + // Presentation Notes: + // Reward pool points + // Essentially we make it so each plank is inflated by the number of points in bonded pool. + // So if we have earned 10 plank and 100 bonded pool points, we get 1,000 reward pool points. + // The delegator scales up their points as well (say 10 for this example) and we get the + // delegator has virtual points of 10points * 10rewards (100reward-points). + // So the payout calc is 100 / 1,000 * 100 = 10 + // + // Keep in mind we subtract the delegators virtual points from the pool points to account + // for the fact that we transferred their portion of rewards out of the pool account. + let u256 = |x| T::BalanceToU256::convert(x); let balance = |x| T::U256ToBalance::convert(x); // If the delegator is unbonding they cannot claim rewards. Note that when the delegator @@ -1649,7 +1672,10 @@ impl Pallet { // The points of the reward pool that belong to the delegator. let delegator_virtual_points = - u256(delegator.points).saturating_mul(u256(new_earnings_since_last_claim)); + // The delegators portion of the reward pool + u256(delegator.points) + // times the amount the pool has earned since the delegator last claimed. + .saturating_mul(u256(new_earnings_since_last_claim)); let delegator_payout = if delegator_virtual_points.is_zero() || current_points.is_zero() || diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 8b33de7da674c..740392aa2a61d 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -196,7 +196,34 @@ mod bonded_pool { } } -mod reward_pool {} +mod reward_pool { + #[test] + fn current_balance_only_counts_balance_over_existential_deposit() { + use super::*; + + ExtBuilder::default().build_and_execute(|| { + let reward_account = Pools::create_reward_account(1); + + // Given + assert_eq!(Balances::free_balance(&reward_account), 0); + + // Then + assert_eq!(RewardPool::::current_balance(1), 0); + + // Given + Balances::make_free_balance_be(&reward_account, Balances::minimum_balance()); + + // Then + assert_eq!(RewardPool::::current_balance(1), 0); + + // Given + Balances::make_free_balance_be(&reward_account, Balances::minimum_balance() + 1); + + // Then + assert_eq!(RewardPool::::current_balance(1), 1); + }); + } +} mod unbond_pool { use super::*; @@ -788,7 +815,7 @@ mod claim_payout { let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); - let reward_account = Pools::create_reward_account(1); + let ed = Balances::minimum_balance(); // Given no rewards have been earned // When @@ -805,7 +832,7 @@ mod claim_payout { assert_eq!(reward_pool, rew(0, 0, 0)); // Given the pool has earned some rewards for the first time - Balances::make_free_balance_be(&reward_account, 5); + Balances::make_free_balance_be(&default_reward_account(), ed + 5); // When let payout = Pools::calculate_delegator_payout( @@ -821,7 +848,7 @@ mod claim_payout { assert_eq!(delegator, del(5)); // Given the pool has earned rewards again - Balances::make_free_balance_be(&reward_account, 10); + Balances::make_free_balance_be(&default_reward_account(), ed + 10); // When let payout = Pools::calculate_delegator_payout( @@ -837,7 +864,7 @@ mod claim_payout { assert_eq!(delegator, del(15)); // Given the pool has earned no new rewards - Balances::make_free_balance_be(&reward_account, 0); + Balances::make_free_balance_be(&default_reward_account(), ed + 0); // When let payout = Pools::calculate_delegator_payout( @@ -861,6 +888,7 @@ mod claim_payout { .build_and_execute(|| { let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); + let ed = Balances::minimum_balance(); // Delegator with 10 points let mut del_10 = Delegators::::get(10).unwrap(); // Delegator with 40 points @@ -872,7 +900,7 @@ mod claim_payout { assert_eq!(del_50.points + del_40.points + del_10.points, 100); assert_eq!(bonded_pool.points, 100); // and the reward pool has earned 100 in rewards - Balances::make_free_balance_be(&default_reward_account(), 100); + Balances::make_free_balance_be(&default_reward_account(), ed + 100); // When let payout = Pools::calculate_delegator_payout( From f8fe93f8667a6c99b6977556acbb1b06e7bc3009 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Mar 2022 21:04:41 +0100 Subject: [PATCH 211/299] Fix test: claim_payout_works --- frame/nomination-pools/src/tests.rs | 48 ++++++++++++++--------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 740392aa2a61d..e10b56fd56b9f 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -576,9 +576,9 @@ mod claim_payout { Balances::make_free_balance_be(&10, 0); Balances::make_free_balance_be(&40, 0); Balances::make_free_balance_be(&50, 0); - let reward_account = Pools::create_reward_account(1); + let ed = Balances::minimum_balance(); // and the reward pool has earned 100 in rewards - Balances::make_free_balance_be(&reward_account, 100); + Balances::make_free_balance_be(&default_reward_account(), ed + 100); // When assert_ok!(Pools::claim_payout(Origin::signed(10))); @@ -592,7 +592,7 @@ mod claim_payout { rew(90, 100 * 100 - 100 * 10, 100) ); assert_eq!(Balances::free_balance(&10), 10); - assert_eq!(Balances::free_balance(&reward_account), 90); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 90); // When assert_ok!(Pools::claim_payout(Origin::signed(40))); @@ -605,7 +605,7 @@ mod claim_payout { rew(50, 9_000 - 100 * 40, 100) ); assert_eq!(Balances::free_balance(&40), 40); - assert_eq!(Balances::free_balance(&reward_account), 50); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 50); // When assert_ok!(Pools::claim_payout(Origin::signed(50))); @@ -615,10 +615,10 @@ mod claim_payout { assert_eq!(Delegators::::get(50).unwrap(), del(50, 100)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 100)); assert_eq!(Balances::free_balance(&50), 50); - assert_eq!(Balances::free_balance(&reward_account), 0); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); // Given the reward pool has some new rewards - Balances::make_free_balance_be(&reward_account, 50); + Balances::make_free_balance_be(&default_reward_account(), ed + 50); // When assert_ok!(Pools::claim_payout(Origin::signed(10))); @@ -628,7 +628,7 @@ mod claim_payout { assert_eq!(Delegators::::get(10).unwrap(), del(10, 150)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(45, 5_000 - 50 * 10, 150)); assert_eq!(Balances::free_balance(&10), 10 + 5); - assert_eq!(Balances::free_balance(&reward_account), 45); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 45); // When assert_ok!(Pools::claim_payout(Origin::signed(40))); @@ -639,11 +639,11 @@ mod claim_payout { assert_eq!(Delegators::::get(40).unwrap(), del(40, 150)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(25, 4_500 - 50 * 40, 150)); assert_eq!(Balances::free_balance(&40), 40 + 20); - assert_eq!(Balances::free_balance(&reward_account), 25); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); // Given del 50 hasn't claimed and the reward pools has just earned 50 - assert_ok!(Balances::mutate_account(&reward_account, |a| a.free += 50)); - assert_eq!(Balances::free_balance(&reward_account), 75); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75); // When assert_ok!(Pools::claim_payout(Origin::signed(50))); @@ -665,7 +665,7 @@ mod claim_payout { ) ); assert_eq!(Balances::free_balance(&50), 50 + 50); - assert_eq!(Balances::free_balance(&reward_account), 25); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); // When assert_ok!(Pools::claim_payout(Origin::signed(10))); @@ -675,11 +675,11 @@ mod claim_payout { assert_eq!(Delegators::::get(10).unwrap(), del(10, 200)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(20, 2_500 - 10 * 50, 200)); assert_eq!(Balances::free_balance(&10), 15 + 5); - assert_eq!(Balances::free_balance(&reward_account), 20); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 20); // Given del 40 hasn't claimed and the reward pool has just earned 400 - assert_ok!(Balances::mutate_account(&reward_account, |a| a.free += 400)); - assert_eq!(Balances::free_balance(&reward_account), 420); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420); // When assert_ok!(Pools::claim_payout(Origin::signed(10))); @@ -700,11 +700,11 @@ mod claim_payout { ) ); assert_eq!(Balances::free_balance(&10), 20 + 40); - assert_eq!(Balances::free_balance(&reward_account), 380); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 380); // Given del 40 + del 50 haven't claimed and the reward pool has earned 20 - assert_ok!(Balances::mutate_account(&reward_account, |a| a.free += 20)); - assert_eq!(Balances::free_balance(&reward_account), 400); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400); // When assert_ok!(Pools::claim_payout(Origin::signed(10))); @@ -718,7 +718,7 @@ mod claim_payout { rew(398, (38_000 + 20 * 100) - 10 * 20, 620) ); assert_eq!(Balances::free_balance(&10), 60 + 2); - assert_eq!(Balances::free_balance(&reward_account), 398); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 398); // When assert_ok!(Pools::claim_payout(Origin::signed(40))); @@ -732,7 +732,7 @@ mod claim_payout { rew(210, 39_800 - 40 * 470, 620) ); assert_eq!(Balances::free_balance(&40), 60 + 188); - assert_eq!(Balances::free_balance(&reward_account), 210); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 210); // When assert_ok!(Pools::claim_payout(Origin::signed(50))); @@ -745,7 +745,7 @@ mod claim_payout { rew(0, 21_000 - 50 * 420, 620) ); assert_eq!(Balances::free_balance(&50), 100 + 210); - assert_eq!(Balances::free_balance(&reward_account), 0); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); }); } @@ -956,7 +956,7 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 50)); // Given the reward pool has some new rewards - Balances::make_free_balance_be(&default_reward_account(), 50); + Balances::make_free_balance_be(&default_reward_account(), ed + 50); // When let payout = Pools::calculate_delegator_payout( @@ -989,7 +989,7 @@ mod claim_payout { // Given del_50 hasn't claimed and the reward pools has just earned 50 assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); - assert_eq!(Balances::free_balance(&default_reward_account()), 75); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75); // When let payout = Pools::calculate_delegator_payout( @@ -1034,7 +1034,7 @@ mod claim_payout { // Given del_40 hasn't claimed and the reward pool has just earned 400 assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); - assert_eq!(Balances::free_balance(&default_reward_account()), 420); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420); // When let payout = Pools::calculate_delegator_payout( @@ -1064,7 +1064,7 @@ mod claim_payout { // Given del_40 + del_50 haven't claimed and the reward pool has earned 20 assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); - assert_eq!(Balances::free_balance(&default_reward_account()), 400); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400); // When let payout = Pools::calculate_delegator_payout( From bd6138931a97fa0ad416a590d988dff6d2bb3e2a Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Mar 2022 21:07:59 +0100 Subject: [PATCH 212/299] do_reward_payout_correctly_sets_pool_state_to_destroying --- frame/nomination-pools/src/lib.rs | 11 ++++++----- frame/nomination-pools/src/tests.rs | 7 +++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 42d2a1ecbda79..afaedf59708d9 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1050,7 +1050,8 @@ pub mod pallet { DoesNotHavePermission, /// Metadata exceeds [`T::MaxMetadataLen`] MetadataExceedsMaxLen, - /// Some error occurred that should never happen. This should be reported to the maintainers. + /// Some error occurred that should never happen. This should be reported to the + /// maintainers. DefensiveError, /// The caller has insufficient balance to create the pool. InsufficientBalanceToCreate, @@ -1408,7 +1409,7 @@ pub mod pallet { ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); ensure!( T::Currency::free_balance(&who) >= - amount.saturating_add(T::Currency::minimum_balance()), + amount.saturating_add(T::Currency::minimum_balance()), Error::::InsufficientBalanceToCreate ); @@ -1635,9 +1636,9 @@ impl Pallet { // Presentation Notes: // Reward pool points // Essentially we make it so each plank is inflated by the number of points in bonded pool. - // So if we have earned 10 plank and 100 bonded pool points, we get 1,000 reward pool points. - // The delegator scales up their points as well (say 10 for this example) and we get the - // delegator has virtual points of 10points * 10rewards (100reward-points). + // So if we have earned 10 plank and 100 bonded pool points, we get 1,000 reward pool + // points. The delegator scales up their points as well (say 10 for this example) and we get + // the delegator has virtual points of 10points * 10rewards (100reward-points). // So the payout calc is 100 / 1,000 * 100 = 10 // // Keep in mind we subtract the delegators virtual points from the pool points to account diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index e10b56fd56b9f..efe26f6059168 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -755,12 +755,11 @@ mod claim_payout { let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); - let reward_account = Pools::create_reward_account(1); - // --- reward_pool.total_earnings saturates + // -- reward_pool.total_earnings saturates // Given - Balances::make_free_balance_be(&reward_account, Balance::MAX); + Balances::make_free_balance_be(&default_reward_account(), Balance::MAX); // When assert_ok!(Pools::do_reward_payout(10, &mut delegator, &mut bonded_pool)); @@ -774,7 +773,7 @@ mod claim_payout { let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); // Force new_earnings * bonded_pool.points == 100 - Balances::make_free_balance_be(&reward_account, 10); + Balances::make_free_balance_be(&default_reward_account(), 5 + 10); assert_eq!(bonded_pool.points, 10); // Force reward_pool.points == U256::MAX - new_earnings * bonded_pool.points reward_pool.points = U256::MAX - U256::from(100u32); From cd2c8ea15c66c3743e07cc57b27d37b8829533b7 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Mar 2022 21:13:15 +0100 Subject: [PATCH 213/299] Remove test do_reward_payout_errors_correctly --- frame/nomination-pools/src/tests.rs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index efe26f6059168..0ed0334a14d4e 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1295,30 +1295,6 @@ mod claim_payout { assert_eq!(Balances::free_balance(&default_reward_account()), 0); }); } - - #[test] - fn do_reward_payout_errors_correctly() { - ExtBuilder::default().build_and_execute(|| { - let mut bonded_pool = BondedPool::::get(1).unwrap(); - let mut delegator = Delegators::::get(10).unwrap(); - - // The only place this can return an error is with the balance transfer from the - // reward account to the delegator, and as far as this comment author can tell this - // can only if storage is in a bad state prior to `do_reward_payout` being called. - - // Given - delegator.points = 15; - assert_eq!(bonded_pool.points, 10); - Balances::make_free_balance_be(&default_reward_account(), 10); - - // Then - // Expect attempt payout of 15/10 * 10 when free balance is actually 10 - assert_noop!( - Pools::do_reward_payout(10, &mut delegator, &mut bonded_pool), - pallet_balances::Error::::InsufficientBalance - ); - }); - } } mod unbond { From 4c652a8ac83bcbc11861bfa302bb5bba93be39d6 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Mar 2022 21:21:12 +0100 Subject: [PATCH 214/299] Fix test: do_reward_payout_works --- frame/nomination-pools/src/tests.rs | 33 +++++++++++++++-------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 0ed0334a14d4e..9f5740f3d85f7 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1116,6 +1116,7 @@ mod claim_payout { .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { let mut bonded_pool = BondedPool::::get(1).unwrap(); + let ed = Balances::minimum_balance(); // Given the bonded pool has 100 points assert_eq!(bonded_pool.points, 100); @@ -1124,7 +1125,7 @@ mod claim_payout { Balances::make_free_balance_be(&40, 0); Balances::make_free_balance_be(&50, 0); // and the reward pool has earned 100 in rewards - Balances::make_free_balance_be(&default_reward_account(), 100); + Balances::make_free_balance_be(&default_reward_account(), ed + 100); let mut del_10 = Delegators::get(10).unwrap(); let mut del_40 = Delegators::get(40).unwrap(); @@ -1142,7 +1143,7 @@ mod claim_payout { rew(90, 100 * 100 - 100 * 10, 100) ); assert_eq!(Balances::free_balance(&10), 10); - assert_eq!(Balances::free_balance(&default_reward_account()), 90); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 90); // When assert_ok!(Pools::do_reward_payout(40, &mut del_40, &mut bonded_pool)); @@ -1155,7 +1156,7 @@ mod claim_payout { rew(50, 9_000 - 100 * 40, 100) ); assert_eq!(Balances::free_balance(&40), 40); - assert_eq!(Balances::free_balance(&default_reward_account()), 50); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 50); // When assert_ok!(Pools::do_reward_payout(50, &mut del_50, &mut bonded_pool)); @@ -1165,10 +1166,10 @@ mod claim_payout { assert_eq!(del_50, del(50, 100)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 100)); assert_eq!(Balances::free_balance(&50), 50); - assert_eq!(Balances::free_balance(&default_reward_account()), 0); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); // Given the reward pool has some new rewards - Balances::make_free_balance_be(&default_reward_account(), 50); + Balances::make_free_balance_be(&default_reward_account(), ed + 50); // When assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); @@ -1178,7 +1179,7 @@ mod claim_payout { assert_eq!(del_10, del(10, 150)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(45, 5_000 - 50 * 10, 150)); assert_eq!(Balances::free_balance(&10), 10 + 5); - assert_eq!(Balances::free_balance(&default_reward_account()), 45); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 45); // When assert_ok!(Pools::do_reward_payout(40, &mut del_40, &mut bonded_pool)); @@ -1189,11 +1190,11 @@ mod claim_payout { assert_eq!(del_40, del(40, 150)); assert_eq!(RewardPools::::get(1).unwrap(), rew(25, 4_500 - 50 * 40, 150)); assert_eq!(Balances::free_balance(&40), 40 + 20); - assert_eq!(Balances::free_balance(&default_reward_account()), 25); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); // Given del 50 hasn't claimed and the reward pools has just earned 50 assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); - assert_eq!(Balances::free_balance(&default_reward_account()), 75); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75); // When assert_ok!(Pools::do_reward_payout(50, &mut del_50, &mut bonded_pool)); @@ -1215,7 +1216,7 @@ mod claim_payout { ) ); assert_eq!(Balances::free_balance(&50), 50 + 50); - assert_eq!(Balances::free_balance(&default_reward_account()), 25); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); // When assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); @@ -1225,11 +1226,11 @@ mod claim_payout { assert_eq!(del_10, del(10, 200)); assert_eq!(RewardPools::::get(1).unwrap(), rew(20, 2_500 - 10 * 50, 200)); assert_eq!(Balances::free_balance(&10), 15 + 5); - assert_eq!(Balances::free_balance(&default_reward_account()), 20); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 20); // Given del 40 hasn't claimed and the reward pool has just earned 400 assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); - assert_eq!(Balances::free_balance(&default_reward_account()), 420); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420); // When assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); @@ -1250,11 +1251,11 @@ mod claim_payout { ) ); assert_eq!(Balances::free_balance(&10), 20 + 40); - assert_eq!(Balances::free_balance(&default_reward_account()), 380); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 380); // Given del 40 + del 50 haven't claimed and the reward pool has earned 20 assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); - assert_eq!(Balances::free_balance(&default_reward_account()), 400); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400); // When assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); @@ -1268,7 +1269,7 @@ mod claim_payout { rew(398, (38_000 + 20 * 100) - 10 * 20, 620) ); assert_eq!(Balances::free_balance(&10), 60 + 2); - assert_eq!(Balances::free_balance(&default_reward_account()), 398); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 398); // When assert_ok!(Pools::do_reward_payout(40, &mut del_40, &mut bonded_pool)); @@ -1282,7 +1283,7 @@ mod claim_payout { rew(210, 39_800 - 40 * 470, 620) ); assert_eq!(Balances::free_balance(&40), 60 + 188); - assert_eq!(Balances::free_balance(&default_reward_account()), 210); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 210); // When assert_ok!(Pools::do_reward_payout(50, &mut del_50, &mut bonded_pool)); @@ -1292,7 +1293,7 @@ mod claim_payout { assert_eq!(del_50, del(50, 620)); assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 21_000 - 50 * 420, 620)); assert_eq!(Balances::free_balance(&50), 100 + 210); - assert_eq!(Balances::free_balance(&default_reward_account()), 0); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); }); } } From 664cfd9c25a72179ec0dc17a58550ac796a0d469 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Mar 2022 21:26:09 +0100 Subject: [PATCH 215/299] Fix test: create_errors_correctly --- frame/nomination-pools/src/tests.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 9f5740f3d85f7..d88f41c2b8688 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -2205,6 +2205,15 @@ mod create { MaxPools::::put(3); MaxDelegators::::put(1); + // Then + assert_noop!( + Pools::create(Origin::signed(11), 20, 11, 11, 11), + Error::::InsufficientBalanceToCreate + ); + + // Given + Balances::make_free_balance_be(&11, 5 + 20); + // Then assert_noop!( Pools::create(Origin::signed(11), 20, 11, 11, 11), From 8c6b57790a29aa600bcbd91d48b0e894fc9d00fe Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Mar 2022 21:43:15 +0100 Subject: [PATCH 216/299] Fix test: create works --- frame/nomination-pools/src/tests.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index d88f41c2b8688..5c72d6511e085 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -2096,13 +2096,14 @@ mod create { ExtBuilder::default().build_and_execute(|| { // next pool id is 2. let next_pool_stash = Pools::create_bonded_account(2); + let ed = Balances::minimum_balance(); assert!(!BondedPools::::contains_key(2)); assert!(!RewardPools::::contains_key(2)); assert!(!Delegators::::contains_key(11)); assert_eq!(StakingMock::active_stake(&next_pool_stash), None); - Balances::make_free_balance_be(&11, StakingMock::minimum_bond()); + Balances::make_free_balance_be(&11, StakingMock::minimum_bond() + ed); assert_ok!(Pools::create( Origin::signed(11), StakingMock::minimum_bond(), @@ -2111,7 +2112,7 @@ mod create { 789 )); - assert_eq!(Balances::free_balance(&11), 0); + assert_eq!(Balances::free_balance(&11), ed + 0); assert_eq!( Delegators::::get(11).unwrap(), Delegator { From 61ea44695e32c10381386cce91b6f68b50fa3952 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 22 Mar 2022 21:48:54 +0100 Subject: [PATCH 217/299] Fix test: unbond_other_of_3_works --- frame/nomination-pools/src/tests.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 5c72d6511e085..19532776b4704 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1334,10 +1334,11 @@ mod unbond { ExtBuilder::default() .add_delegators(vec![(40, 40), (550, 550)]) .build_and_execute(|| { + let ed = Balances::minimum_balance(); // Given a slash from 600 -> 100 StakingMock::set_bonded_balance(default_bonded_account(), 100); // and unclaimed rewards of 600. - Balances::make_free_balance_be(&default_reward_account(), 600); + Balances::make_free_balance_be(&default_reward_account(), ed + 600); // When assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); From a0c7fbea43f9f3f629dd14ca4139b4fd0e75514f Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 23 Mar 2022 11:28:28 +0100 Subject: [PATCH 218/299] Ensure that ED is transferred into reward pool upon creation --- .../nomination-pools/benchmarking/src/lib.rs | 13 +++++------ frame/nomination-pools/src/lib.rs | 17 ++++++++++---- frame/nomination-pools/src/tests.rs | 22 +++++++++---------- frame/support/src/lib.rs | 2 +- 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 64a8eef6396df..8819d2520247a 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -48,8 +48,9 @@ fn create_pool_account( n: u32, balance: BalanceOf, ) -> (T::AccountId, T::AccountId) { + let ed = CurrencyOf::::minimum_balance(); let pool_creator: T::AccountId = - create_funded_user_with_balance::("pool_creator", n, balance * 2u32.into()); + create_funded_user_with_balance::("pool_creator", n, ed + balance * 2u32.into()); Pools::::create( Origin::Signed(pool_creator.clone()).into(), @@ -92,10 +93,6 @@ impl ListScenario { /// of storage reads and writes. /// /// - the destination bag has at least one node, which will need its next pointer updated. - /// - /// NOTE: while this scenario specifically targets a worst case for the bags-list, it should - /// also elicit a worst case for other known `SortedListProvider` implementations; although - /// this may not be true against unknown `SortedListProvider` implementations. pub(crate) fn new( origin_weight: BalanceOf, is_increase: bool, @@ -219,12 +216,12 @@ frame_benchmarking::benchmarks! { claim_payout { let origin_weight = pallet_nomination_pools::MinCreateBond::::get().max(CurrencyOf::::minimum_balance()) * 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); // Send funds to the reward account of the pool - CurrencyOf::::make_free_balance_be(&reward_account, origin_weight); + CurrencyOf::::make_free_balance_be(&reward_account, ed + origin_weight); // Sanity check assert_eq!( @@ -241,7 +238,7 @@ frame_benchmarking::benchmarks! { ); assert_eq!( CurrencyOf::::free_balance(&reward_account), - Zero::zero() + ed + Zero::zero() ); } diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index afaedf59708d9..fe5d2726cd93a 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1344,10 +1344,6 @@ pub mod pallet { // Kill accounts from storage by making their balance go below ED. We assume that // the accounts have no references that would prevent destruction once we get to // this point. - debug_assert_eq!( - T::Currency::free_balance(&bonded_pool.reward_account()), - Zero::zero() - ); debug_assert_eq!( T::Currency::free_balance(&bonded_pool.bonded_account()), Zero::zero() @@ -1383,6 +1379,11 @@ pub mod pallet { /// * `root` - The account to set as [`PoolRoles::root`]. /// * `nominator` - The account to set as the [`PoolRoles::nominator`]. /// * `state_toggler` - The account to set as the [`PoolRoles::state_toggler`]. + /// + /// # Note + /// + /// In addition to `amount`, the caller will transfer the existential deposit; so the caller + /// needs at have at least `amount + existential_deposit` transferrable. #[pallet::weight(T::WeightInfo::create())] #[frame_support::transactional] pub fn create( @@ -1424,6 +1425,14 @@ pub mod pallet { let points = bonded_pool.try_bond_delegator(&who, amount, PoolBond::Create)?; + T::Currency::transfer( + &who, + &bonded_pool.reward_account(), + T::Currency::minimum_balance(), + ExistenceRequirement::AllowDeath, + ) + .defensive()?; + Delegators::::insert( who.clone(), Delegator:: { diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 19532776b4704..f6f84d01cea66 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -202,25 +202,25 @@ mod reward_pool { use super::*; ExtBuilder::default().build_and_execute(|| { - let reward_account = Pools::create_reward_account(1); + let reward_account = Pools::create_reward_account(2); // Given assert_eq!(Balances::free_balance(&reward_account), 0); // Then - assert_eq!(RewardPool::::current_balance(1), 0); + assert_eq!(RewardPool::::current_balance(2), 0); // Given Balances::make_free_balance_be(&reward_account, Balances::minimum_balance()); // Then - assert_eq!(RewardPool::::current_balance(1), 0); + assert_eq!(RewardPool::::current_balance(2), 0); // Given Balances::make_free_balance_be(&reward_account, Balances::minimum_balance() + 1); // Then - assert_eq!(RewardPool::::current_balance(1), 1); + assert_eq!(RewardPool::::current_balance(2), 1); }); } } @@ -1721,7 +1721,7 @@ mod withdraw_unbonded_other { assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(10), 10, 0)); // Then - assert_eq!(Balances::free_balance(&10), 10 + 10); + assert_eq!(Balances::free_balance(&10), 5 + 10); assert_eq!(Balances::free_balance(&default_bonded_account()), 0); assert!(!Delegators::::contains_key(10)); // Pools are removed from storage because the depositor left @@ -1786,7 +1786,7 @@ mod withdraw_unbonded_other { assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(10), 10, 0)); // Then - assert_eq!(Balances::free_balance(&10), 10 + 0); + assert_eq!(Balances::free_balance(&10), 5 + 0); assert_eq!(Balances::free_balance(&default_bonded_account()), 0); assert!(!Delegators::::contains_key(10)); // Pools are removed from storage because the depositor left @@ -1801,7 +1801,7 @@ mod withdraw_unbonded_other { ExtBuilder::default().build_and_execute(|| { // Given assert_eq!(Balances::minimum_balance(), 5); - assert_eq!(Balances::free_balance(&10), 10); + assert_eq!(Balances::free_balance(&10), 5); assert_eq!(Balances::free_balance(&default_bonded_account()), 10); unsafe_set_state(1, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); @@ -1820,7 +1820,7 @@ mod withdraw_unbonded_other { assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(10), 10, 0)); // Then - assert_eq!(Balances::free_balance(10), 10 + 5); + assert_eq!(Balances::free_balance(10), 5 + 5); assert_eq!(Balances::free_balance(&default_bonded_account()), 0); }); } @@ -2029,7 +2029,7 @@ mod withdraw_unbonded_other { // The depositor can withdraw assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 10, 0)); assert!(!Delegators::::contains_key(10)); - assert_eq!(Balances::free_balance(10), 10 + 10); + assert_eq!(Balances::free_balance(10), 10 + 10 - Balances::minimum_balance()); // Pools are removed from storage because the depositor left assert!(!SubPoolsStorage::::contains_key(1)); assert!(!RewardPools::::contains_key(1)); @@ -2080,7 +2080,7 @@ mod withdraw_unbonded_other { // The depositor can withdraw assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 10, 0)); assert!(!Delegators::::contains_key(10)); - assert_eq!(Balances::free_balance(10), 10 + 10); + assert_eq!(Balances::free_balance(10), 5 + 10); // Pools are removed from storage because the depositor left assert!(!SubPoolsStorage::::contains_key(1)); assert!(!RewardPools::::contains_key(1)); @@ -2113,7 +2113,7 @@ mod create { 789 )); - assert_eq!(Balances::free_balance(&11), ed + 0); + assert_eq!(Balances::free_balance(&11), 0); assert_eq!( Delegators::::get(11).unwrap(), Delegator { diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 3988b5e9af219..14e82a0c82526 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -200,7 +200,7 @@ macro_rules! generate_storage_alias { => DoubleMap<($key1:ty, $hasher1:ty), ($key2:ty, $hasher2:ty), $value:ty $(, $querytype:ty)?> ) => { $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); + $crate::generate_storage_alias!(@ $pallet, $name); type $name = $crate::storage::types::StorageDoubleMap< [<$name Instance>], $hasher1, From 77f6ef80822d42f1048812c159c3cc0e06b3a73f Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 23 Mar 2022 13:05:30 +0100 Subject: [PATCH 219/299] WIP pool lifecycle test --- frame/nomination-pools/src/tests.rs | 56 +++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index f6f84d01cea66..ee3abdb6cb031 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -52,13 +52,55 @@ fn test_setup_works() { } #[test] -fn exercise_delegator_life_cycle() { - // todo!() - // create pool - // join pool - // claim rewards - // get more rewards - // +fn exercise_pool_life_cycle() { + ExtBuilder::default().build_and_execute(|| { + let bonded_pool_account = Pools::create_bonded_account(2); + let reward_account = Pools::create_reward_account(2); + let ed = Balances::minimum_balance(); + let min_create_bond = MinCreateBond::::get().max(StakingMock::minimum_bond()); + let min_join_bond = MinJoinBond::::get(); + + // Create a pool + Balances::make_free_balance_be(&11, ed + min_create_bond); + assert_ok!(Pools::create( + Origin::signed(11), + min_create_bond, + 101, + 102, + 103, + )); + + assert_eq!(Balances::free_balance(&11), 0); + assert_eq!(Balances::free_balance(&reward_account), ed); + assert_eq!(Balances::free_balance(&bonded_pool_account), min_create_bond); + + // A delegator joins the pool + Balances::make_free_balance_be(&12, ed + min_join_bond); + + assert_ok!(Pools::join( + Origin::signed(12), + MinJoinBond::::get(), + 2 + )); + + assert_eq!(Balances::free_balance(&12), ed); + assert_eq!(Balances::free_balance(&bonded_pool_account), min_create_bond + min_join_bond); + + // The pools gets some rewards + assert_ok!(Balances::mutate_account(&reward_account, |a| a.free += 50)); + + let reward_for_11 = 50 * min_create_bond / (min_join_bond + min_create_bond); + let reward_for_12 = 50 * min_join_bond / (min_join_bond + min_create_bond); + + assert_ok!(Pools::claim_payout(Origin::signed(11))); + assert_ok!(Pools::claim_payout(Origin::signed(12))); + + assert_eq!(Balances::free_balance(&11), ed + reward_for_11); + // The rewards are slightly lossy + assert_eq!(Balances::free_balance(&12), ed + reward_for_12 - 1); + + // WIP + }); } mod bonded_pool { From ff973be07b907f1ccf2486e3bfa278479402162d Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 23 Mar 2022 13:32:11 +0100 Subject: [PATCH 220/299] Fix benchmarks --- frame/nomination-pools/benchmarking/src/lib.rs | 8 ++++---- frame/nomination-pools/benchmarking/src/mock.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 8819d2520247a..43d4ff61e34fd 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -264,7 +264,7 @@ frame_benchmarking::benchmarks! { &delegator_id ) .unwrap(); - assert_eq!(delegator.unbonding_era, Some(0)); + assert_eq!(delegator.unbonding_era, Some(0 + T::StakingInterface::bonding_duration())); } pool_withdraw_unbonded { @@ -382,10 +382,8 @@ frame_benchmarking::benchmarks! { // should never exist by time the depositor withdraws so we test that it gets cleaned // up when unbonding. let reward_account = Pools::::create_reward_account(1); - CurrencyOf::::make_free_balance_be(&reward_account, CurrencyOf::::minimum_balance()); assert!(frame_system::Account::::contains_key(&reward_account)); Pools::::unbond_other(Origin::Signed(depositor.clone()).into(), depositor.clone()).unwrap(); - assert!(!frame_system::Account::::contains_key(&reward_account)); // Sanity check that unbond worked assert_eq!( @@ -407,6 +405,7 @@ frame_benchmarking::benchmarks! { assert!(SubPoolsStorage::::contains_key(&1)); assert!(RewardPools::::contains_key(&1)); assert!(Delegators::::contains_key(&depositor)); + assert!(frame_system::Account::::contains_key(&reward_account)); whitelist_account!(depositor); }: withdraw_unbonded_other(Origin::Signed(depositor.clone()), depositor.clone(), s) @@ -418,12 +417,13 @@ frame_benchmarking::benchmarks! { assert!(!RewardPools::::contains_key(&1)); assert!(!Delegators::::contains_key(&depositor)); assert!(!frame_system::Account::::contains_key(&pool_account)); + assert!(!frame_system::Account::::contains_key(&reward_account)); // Funds where transferred back correctly assert_eq!( CurrencyOf::::free_balance(&depositor), // gets bond back + rewards collecting when unbonding - min_create_bond * 2u32.into() + CurrencyOf::::minimum_balance() + min_create_bond * 2u32.into() ); } diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 9f0c7b09bf121..022be21c902d3 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -98,7 +98,7 @@ impl pallet_staking::Config for Runtime { type SessionsPerEra = (); type SlashDeferDuration = (); type SlashCancelOrigin = frame_system::EnsureRoot; - type BondingDuration = (); + type BondingDuration = ConstU32<3>; type SessionInterface = (); type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = (); From 0c5e23c142ce9762072b495bb4c6939ef6cbd75c Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 23 Mar 2022 15:00:23 +0100 Subject: [PATCH 221/299] Add sanity check for ED + reward pools --- .../nomination-pools/benchmarking/src/lib.rs | 13 +--- .../nomination-pools/benchmarking/src/mock.rs | 2 +- frame/nomination-pools/src/lib.rs | 18 +++-- frame/nomination-pools/src/tests.rs | 78 ++++++++----------- 4 files changed, 50 insertions(+), 61 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 43d4ff61e34fd..cf5a1c7199802 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -1,8 +1,5 @@ //! Benchmarks for the nomination pools coupled with the staking and bags list pallets. -// Ensure we're `no_std` when compiling for Wasm. -#![cfg_attr(not(feature = "std"), no_std)] - #[cfg(test)] mod mock; @@ -150,8 +147,8 @@ impl ListScenario { fn add_joiner(mut self, amount: BalanceOf) -> Self { let amount = MinJoinBond::::get() .max(CurrencyOf::::minimum_balance()) - // Max the `given` amount with minimum thresholds for account balance and joining a pool - // to ensure 1. the user can be created and 2. can join the pool + // Max `amount` with minimum thresholds for account balance and joining a pool + // to ensure 1) the user can be created and 2) can join the pool .max(amount); let joiner: T::AccountId = account("joiner", USER_SEED, 0); @@ -243,7 +240,7 @@ frame_benchmarking::benchmarks! { } unbond_other { - // the weight the nominator will start at. The value used here is expected to be + // The weight the nominator will start at. The value used here is expected to be // significantly higher than the first position in a list (e.g. the first bag threshold). let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) .map_err(|_| "balance expected to be a u128") @@ -258,7 +255,7 @@ frame_benchmarking::benchmarks! { }: _(Origin::Signed(delegator_id.clone()), delegator_id.clone()) verify { let bonded_after = T::StakingInterface::active_stake(&scenario.origin1).unwrap(); - // We at least went down to the destination bag, (if not an even lower bag) + // We at least went down to the destination bag assert!(bonded_after <= scenario.dest_weight.clone()); let delegator = Delegators::::get( &delegator_id @@ -562,8 +559,6 @@ frame_benchmarking::benchmarks! { } } -// TODO: consider benchmarking slashing logic with pools - frame_benchmarking::impl_benchmark_test_suite!( Pallet, crate::mock::new_test_ext(), diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 022be21c902d3..19776f8be4675 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -34,7 +34,7 @@ impl frame_system::Config for Runtime { type BlockNumber = BlockNumber; type Call = Call; type Hash = sp_core::H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; + type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Header = sp_runtime::testing::Header; diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index fe5d2726cd93a..ebb34553ad01e 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1,9 +1,7 @@ //! # Nomination Pools for Staking Delegation //! -//! A pallet that allows delegators to delegate their stake to nominating pools, each of which acts -//! as a nominator and nominates validators on the delegators behalf. -//! -//! # Index +//! A pallet that allows delegators to delegate their stake to nominating pools. A nomination pool +//! acts as nominator and nominates validators on the delegators behalf. # Index //! //! * [Key terms](#key-terms) //! * [Usage](#usage) @@ -19,7 +17,7 @@ //! [`SubPools`] and [`SubPoolsStorage`]. Sub pools are identified via the pools bonded account. //! * delegators: Accounts that are members of pools. See [`Delegator`] and [`Delegators`]. //! Delegators are identified via their account. -//! * point: A measure of a delegators portion of a pools funds. +//! * point: A unit of measure for a delegators portion of a pool's funds. //! //! ## Usage //! @@ -71,7 +69,7 @@ //! [`Call::unbond_other`] and [`Call::withdraw_unbonded_other`]. Once a pool is destroying state, //! it cannot be reverted to another state. //! -//! A pool has 3 administrative positions (see [`BondedPool`]): +//! A pool has 3 administrative roles (see [`PoolRoles`]): //! //! * Depositor: creates the pool and is the initial delegator. They can only leave pool once all //! other delegators have left. Once they fully leave the pool is destroyed. @@ -373,13 +371,14 @@ pub enum PoolState { Open, /// The pool is blocked. No one else can join. Blocked, - /// The pool has been scheduled to destroyed. + /// The pool is in the process of being destroyed. /// /// All delegators can now be permissionlessly unbonded, and the pool can never go back to any /// other state other than being dissolved. Destroying, } +/// Pool adminstration roles. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone)] pub struct PoolRoles { /// Creates the pool and is the initial delegator. They can only leave the pool once all @@ -1804,6 +1803,11 @@ impl Pallet { assert!(MaxPools::::get().map_or(true, |max| bonded_pools.len() <= (max as usize))); + for id in reward_pools { + let account = Self::create_reward_account(id); + assert!(T::Currency::free_balance(&account) >= T::Currency::minimum_balance()); + } + let mut pools_delegators = BTreeMap::::new(); let mut all_delegators = 0u32; Delegators::::iter().for_each(|(_, d)| { diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index ee3abdb6cb031..eb7d53a83b6ad 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -54,50 +54,40 @@ fn test_setup_works() { #[test] fn exercise_pool_life_cycle() { ExtBuilder::default().build_and_execute(|| { - let bonded_pool_account = Pools::create_bonded_account(2); - let reward_account = Pools::create_reward_account(2); - let ed = Balances::minimum_balance(); - let min_create_bond = MinCreateBond::::get().max(StakingMock::minimum_bond()); - let min_join_bond = MinJoinBond::::get(); - - // Create a pool - Balances::make_free_balance_be(&11, ed + min_create_bond); - assert_ok!(Pools::create( - Origin::signed(11), - min_create_bond, - 101, - 102, - 103, - )); - - assert_eq!(Balances::free_balance(&11), 0); - assert_eq!(Balances::free_balance(&reward_account), ed); - assert_eq!(Balances::free_balance(&bonded_pool_account), min_create_bond); - - // A delegator joins the pool - Balances::make_free_balance_be(&12, ed + min_join_bond); - - assert_ok!(Pools::join( - Origin::signed(12), - MinJoinBond::::get(), - 2 - )); - - assert_eq!(Balances::free_balance(&12), ed); - assert_eq!(Balances::free_balance(&bonded_pool_account), min_create_bond + min_join_bond); - - // The pools gets some rewards - assert_ok!(Balances::mutate_account(&reward_account, |a| a.free += 50)); - - let reward_for_11 = 50 * min_create_bond / (min_join_bond + min_create_bond); - let reward_for_12 = 50 * min_join_bond / (min_join_bond + min_create_bond); - - assert_ok!(Pools::claim_payout(Origin::signed(11))); - assert_ok!(Pools::claim_payout(Origin::signed(12))); - - assert_eq!(Balances::free_balance(&11), ed + reward_for_11); - // The rewards are slightly lossy - assert_eq!(Balances::free_balance(&12), ed + reward_for_12 - 1); + // let bonded_pool_account = Pools::create_bonded_account(2); + // let reward_account = Pools::create_reward_account(2); + // let ed = Balances::minimum_balance(); + // let min_create_bond = MinCreateBond::::get().max(StakingMock::minimum_bond()); + // let min_join_bond = MinJoinBond::::get(); + + // // Create a pool + // Balances::make_free_balance_be(&11, ed + min_create_bond); + // assert_ok!(Pools::create(Origin::signed(11), min_create_bond, 101, 102, 103,)); + + // assert_eq!(Balances::free_balance(&11), 0); + // assert_eq!(Balances::free_balance(&reward_account), ed); + // assert_eq!(Balances::free_balance(&bonded_pool_account), min_create_bond); + + // // A delegator joins the pool + // Balances::make_free_balance_be(&12, ed + min_join_bond); + + // assert_ok!(Pools::join(Origin::signed(12), MinJoinBond::::get(), 2)); + + // assert_eq!(Balances::free_balance(&12), ed); + // assert_eq!(Balances::free_balance(&bonded_pool_account), min_create_bond + min_join_bond); + + // // The pools gets some rewards + // assert_ok!(Balances::mutate_account(&reward_account, |a| a.free += 50)); + + // let reward_for_11 = 50 * min_create_bond / (min_join_bond + min_create_bond); + // let reward_for_12 = 50 * min_join_bond / (min_join_bond + min_create_bond); + + // assert_ok!(Pools::claim_payout(Origin::signed(11))); + // assert_ok!(Pools::claim_payout(Origin::signed(12))); + + // // The rewards are slightly lossy + // assert_eq!(Balances::free_balance(&11), ed + reward_for_11 - 5); + // assert_eq!(Balances::free_balance(&12), ed + reward_for_12 + 1); // WIP }); From 28178e982244c95dba766b931c8c625c63c026e9 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 29 Mar 2022 01:14:04 +0100 Subject: [PATCH 222/299] `bond_extra` for nomination pools (#11100) * bond_extra for nomination pools * Update frame/nomination-pools/src/lib.rs * Update frame/nomination-pools/src/lib.rs * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * add benchmarks * remove the min logic of bond_extra Co-authored-by: Zeke Mostov --- frame/bags-list/src/benchmarks.rs | 12 +- .../nomination-pools/benchmarking/src/lib.rs | 64 +++- frame/nomination-pools/src/lib.rs | 205 ++++++++---- frame/nomination-pools/src/mock.rs | 19 ++ frame/nomination-pools/src/tests.rs | 314 ++++++++++++++---- frame/support/src/traits/misc.rs | 13 +- 6 files changed, 492 insertions(+), 135 deletions(-) diff --git a/frame/bags-list/src/benchmarks.rs b/frame/bags-list/src/benchmarks.rs index b94a97093d001..573b467dc4586 100644 --- a/frame/bags-list/src/benchmarks.rs +++ b/frame/bags-list/src/benchmarks.rs @@ -175,10 +175,10 @@ frame_benchmarking::benchmarks! { vec![heavier, lighter, heavier_prev, heavier_next] ) } -} -frame_benchmarking::impl_benchmark_test_suite!( - Pallet, - crate::mock::ExtBuilder::default().skip_genesis_ids().build(), - crate::mock::Runtime -); + impl_benchmark_test_suite!( + Pallet, + crate::mock::ExtBuilder::default().skip_genesis_ids().build(), + crate::mock::Runtime + ); +} diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 43d4ff61e34fd..18a7e802945c7 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -11,7 +11,7 @@ use frame_election_provider_support::SortedListProvider; use frame_support::{ensure, traits::Get}; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ - BalanceOf, BondedPoolInner, BondedPools, ConfigOp, Delegators, MaxDelegators, + BalanceOf, BondExtra, BondedPoolInner, BondedPools, ConfigOp, Delegators, MaxDelegators, MaxDelegatorsPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, Pallet as Pools, PoolRoles, PoolState, RewardPools, SubPoolsStorage, }; @@ -75,9 +75,11 @@ fn vote_to_balance( vote.try_into().map_err(|_| "could not convert u64 to Balance") } +#[allow(unused)] struct ListScenario { /// Stash/Controller that is expected to be moved. origin1: T::AccountId, + creator1: T::AccountId, dest_weight: BalanceOf, origin1_delegator: Option, } @@ -109,7 +111,7 @@ impl ListScenario { sp_std::mem::forget(i); // Create accounts with the origin weight - let (_, pool_origin1) = create_pool_account::(USER_SEED + 1, origin_weight); + let (pool_creator1, pool_origin1) = create_pool_account::(USER_SEED + 1, origin_weight); T::StakingInterface::nominate( pool_origin1.clone(), // NOTE: these don't really need to be validators. @@ -144,7 +146,12 @@ impl ListScenario { assert_eq!(vote_to_balance::(weight_of(&pool_origin2)).unwrap(), origin_weight); assert_eq!(vote_to_balance::(weight_of(&pool_dest1)).unwrap(), dest_weight); - Ok(ListScenario { origin1: pool_origin1, dest_weight, origin1_delegator: None }) + Ok(ListScenario { + origin1: pool_origin1, + creator1: pool_creator1, + dest_weight, + origin1_delegator: None, + }) } fn add_joiner(mut self, amount: BalanceOf) -> Self { @@ -214,6 +221,43 @@ frame_benchmarking::benchmarks! { ); } + bond_extra_transfer { + let origin_weight = pallet_nomination_pools::MinCreateBond::::get() + .max(CurrencyOf::::minimum_balance()) + * 2u32.into(); + let scenario = ListScenario::::new(origin_weight, true)?; + let extra = scenario.dest_weight.clone() - origin_weight; + + // creator of the src pool will bond-extra, bumping itself to dest bag. + + }: bond_extra(Origin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra)) + verify { + assert_eq!( + T::StakingInterface::active_stake(&scenario.origin1).unwrap(), + scenario.dest_weight + ); + } + + bond_extra_reward { + let origin_weight = pallet_nomination_pools::MinCreateBond::::get() + .max(CurrencyOf::::minimum_balance()) + * 2u32.into(); + let scenario = ListScenario::::new(origin_weight, true)?; + let extra = (scenario.dest_weight.clone() - origin_weight).max(CurrencyOf::::minimum_balance()); + + // transfer exactly `extra` to the depositor of the src pool (1), + let reward_account1 = Pools::::create_reward_account(1); + assert!(extra >= CurrencyOf::::minimum_balance()); + CurrencyOf::::deposit_creating(&reward_account1, extra); + + }: bond_extra(Origin::Signed(scenario.creator1.clone()), BondExtra::Rewards) + verify { + assert_eq!( + T::StakingInterface::active_stake(&scenario.origin1).unwrap(), + scenario.dest_weight + ); + } + claim_payout { let origin_weight = pallet_nomination_pools::MinCreateBond::::get().max(CurrencyOf::::minimum_balance()) * 2u32.into(); let ed = CurrencyOf::::minimum_balance(); @@ -560,12 +604,10 @@ frame_benchmarking::benchmarks! { assert_eq!(MaxDelegators::::get(), Some(u32::MAX)); assert_eq!(MaxDelegatorsPerPool::::get(), Some(u32::MAX)); } -} - -// TODO: consider benchmarking slashing logic with pools -frame_benchmarking::impl_benchmark_test_suite!( - Pallet, - crate::mock::new_test_ext(), - crate::mock::Runtime -); + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext(), + crate::mock::Runtime + ); +} diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index fe5d2726cd93a..342d27eefa8d2 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -333,10 +333,21 @@ pub enum ConfigOp { Remove, } -/// Extrinsics that bond some funds to the pool. -enum PoolBond { +/// The type of binding that can happen to a pool. +enum BondType { + /// Someone is bonding into the pool upon creation. Create, - Join, + /// Someone is adding more funds later to this pool. + Later, +} + +/// How to increase the bond of a delegator. +#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] +pub enum BondExtra { + /// Take from the free balance. + FreeBalance(Balance), + /// Take the entire amount from the accumulated rewards. + Rewards, } /// The type of account being created. @@ -658,30 +669,26 @@ impl BondedPool { } } - /// Try to transfer a delegators funds to the bonded pool account and then try to bond them. - /// Additionally, this increments the delegator counter for the pool. + /// Bond exactly `amount` from `who`'s funds into this pool. /// - /// # Warning + /// If the bond type is `Create`, `StakingInterface::bond` is called, and `who` + /// is allowed to be killed. Otherwise, `StakingInterface::bond_extra` is called and `who` + /// cannot be killed. /// - /// This must only be used inside of transactional extrinsic, as funds are transferred prior to - /// attempting a fallible bond. - fn try_bond_delegator( + /// Returns `Ok(points_issues)`, `Err` otherwise. + fn try_bond_funds( &mut self, who: &T::AccountId, amount: BalanceOf, - ty: PoolBond, + ty: BondType, ) -> Result, DispatchError> { - self.try_inc_delegators()?; - - // Transfer the funds to be bonded from `who` to the pools account so the pool can then - // go bond them. T::Currency::transfer( &who, &self.bonded_account(), amount, match ty { - PoolBond::Create => ExistenceRequirement::AllowDeath, - PoolBond::Join => ExistenceRequirement::KeepAlive, + BondType::Create => ExistenceRequirement::AllowDeath, + BondType::Later => ExistenceRequirement::KeepAlive, }, )?; // We must calculate the points issued *before* we bond who's funds, else points:balance @@ -689,9 +696,8 @@ impl BondedPool { let points_issued = self.issue(amount); match ty { - PoolBond::Create => T::StakingInterface::bond( + BondType::Create => T::StakingInterface::bond( self.bonded_account(), - // We make the stash and controller the same for simplicity self.bonded_account(), amount, self.reward_account(), @@ -699,7 +705,7 @@ impl BondedPool { // The pool should always be created in such a way its in a state to bond extra, but if // the active balance is slashed below the minimum bonded or the account cannot be // found, we exit early. - PoolBond::Join => T::StakingInterface::bond_extra(self.bonded_account(), amount)?, + BondType::Later => T::StakingInterface::bond_extra(self.bonded_account(), amount)?, } Ok(points_issued) @@ -720,7 +726,10 @@ impl BondedPool { fn set_state(&mut self, state: PoolState) { if self.state != state { self.state = state; - Pallet::::deposit_event(Event::::State { pool_id: self.id, new_state: state }); + Pallet::::deposit_event(Event::::StateChanged { + pool_id: self.id, + new_state: state, + }); }; } } @@ -853,6 +862,7 @@ impl Get for TotalUnbondingPools { #[frame_support::pallet] pub mod pallet { use super::*; + use frame_support::transactional; use frame_system::{ensure_signed, pallet_prelude::*}; #[pallet::pallet] @@ -990,16 +1000,24 @@ pub mod pallet { } } + /// Events of this pallet. #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { + /// A pool has been created. Created { depositor: T::AccountId, pool_id: PoolId }, - Joined { delegator: T::AccountId, pool_id: PoolId, bonded: BalanceOf }, + /// A delegator has became bonded in a pool. + Bonded { delegator: T::AccountId, pool_id: PoolId, bonded: BalanceOf, joined: bool }, + /// A payout has been made to a delegator. PaidOut { delegator: T::AccountId, pool_id: PoolId, payout: BalanceOf }, + /// A delegator has unbonded from their pool. Unbonded { delegator: T::AccountId, pool_id: PoolId, amount: BalanceOf }, + /// A delegator has withdrawn from their pool. Withdrawn { delegator: T::AccountId, pool_id: PoolId, amount: BalanceOf }, + /// A pool has been destroyed. Destroyed { pool_id: PoolId }, - State { pool_id: PoolId, new_state: PoolState }, + /// The state of a pool has changed + StateChanged { pool_id: PoolId, new_state: PoolState }, } #[pallet::error] @@ -1085,7 +1103,8 @@ pub mod pallet { let reward_pool = RewardPool::::get_and_update(pool_id) .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; - let points_issued = bonded_pool.try_bond_delegator(&who, amount, PoolBond::Join)?; + bonded_pool.try_inc_delegators()?; + let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Later)?; Delegators::insert( who.clone(), @@ -1103,7 +1122,52 @@ pub mod pallet { }, ); bonded_pool.put(); - Self::deposit_event(Event::::Joined { delegator: who, pool_id, bonded: amount }); + Self::deposit_event(Event::::Bonded { + delegator: who, + pool_id, + bonded: amount, + joined: true, + }); + + Ok(()) + } + + /// Bond `extra` more funds from `origin` into the pool to which they already belong. + /// + /// Additional funds can come from either the free balance of the account, of from the + /// accumulated rewards, see [`BondExtra`]. + // NOTE: this transaction is implemented with the sole purpose of readability and + // correctness, not optimization. We read/write several storage items multiple times instead + // of just once, in the spirit reusing code. + #[pallet::weight(0)] + #[transactional] + pub fn bond_extra(origin: OriginFor, extra: BondExtra>) -> DispatchResult { + let who = ensure_signed(origin)?; + let (mut delegator, mut bonded_pool, mut reward_pool) = + Self::get_delegator_with_pools(&who)?; + + let (points_issued, bonded) = match extra { + BondExtra::FreeBalance(amount) => + (bonded_pool.try_bond_funds(&who, amount, BondType::Later)?, amount), + BondExtra::Rewards => { + let claimed = Self::do_reward_payout( + &who, + &mut delegator, + &mut bonded_pool, + &mut reward_pool, + )?; + (bonded_pool.try_bond_funds(&who, claimed, BondType::Later)?, claimed) + }, + }; + delegator.points = delegator.points.saturating_add(points_issued); + + Self::deposit_event(Event::::Bonded { + delegator: who.clone(), + pool_id: delegator.pool_id, + bonded, + joined: false, + }); + Self::put_delegator_with_pools(&who, delegator, bonded_pool, reward_pool); Ok(()) } @@ -1117,22 +1181,13 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::claim_payout())] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let mut delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; - let mut bonded_pool = BondedPool::::get(delegator.pool_id) - .defensive_ok_or_else(|| Error::::PoolNotFound)?; - let was_destroying = bonded_pool.is_destroying(); - - Self::do_reward_payout(who.clone(), &mut delegator, &mut bonded_pool)?; + let (mut delegator, mut bonded_pool, mut reward_pool) = + Self::get_delegator_with_pools(&who)?; - if bonded_pool.is_destroying() && !was_destroying { - Self::deposit_event(Event::::State { - pool_id: delegator.pool_id, - new_state: PoolState::Destroying, - }); - } - bonded_pool.put(); - Delegators::insert(who, delegator); + let _ = + Self::do_reward_payout(&who, &mut delegator, &mut bonded_pool, &mut reward_pool)?; + Self::put_delegator_with_pools(&who, delegator, bonded_pool, reward_pool); Ok(()) } @@ -1164,16 +1219,20 @@ pub mod pallet { delegator_account: T::AccountId, ) -> DispatchResult { let caller = ensure_signed(origin)?; - let mut delegator = - Delegators::::get(&delegator_account).ok_or(Error::::DelegatorNotFound)?; - let mut bonded_pool = BondedPool::::get(delegator.pool_id) - .defensive_ok_or_else(|| Error::::PoolNotFound)?; + let (mut delegator, mut bonded_pool, mut reward_pool) = + Self::get_delegator_with_pools(&delegator_account)?; + bonded_pool.ok_to_unbond_other_with(&caller, &delegator_account, &delegator)?; // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the bonded pool and thus they can no longer claim their payouts. // It is not strictly necessary to claim the rewards, but we do it here for UX. - Self::do_reward_payout(delegator_account.clone(), &mut delegator, &mut bonded_pool)?; + Self::do_reward_payout( + &delegator_account, + &mut delegator, + &mut bonded_pool, + &mut reward_pool, + )?; let balance_to_unbond = bonded_pool.balance_to_unbond(delegator.points); @@ -1217,9 +1276,8 @@ pub mod pallet { }); // Now that we know everything has worked write the items to storage. - bonded_pool.put(); SubPoolsStorage::insert(&delegator.pool_id, sub_pools); - Delegators::insert(delegator_account, delegator); + Self::put_delegator_with_pools(&delegator_account, delegator, bonded_pool, reward_pool); Ok(()) } @@ -1423,7 +1481,8 @@ pub mod pallet { PoolRoles { root, nominator, state_toggler, depositor: who.clone() }, ); - let points = bonded_pool.try_bond_delegator(&who, amount, PoolBond::Create)?; + bonded_pool.try_inc_delegators()?; + let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?; T::Currency::transfer( &who, @@ -1577,7 +1636,6 @@ pub mod pallet { impl Pallet { /// Create the main, bonded account of a pool with the given id. - pub fn create_bonded_account(id: PoolId) -> T::AccountId { T::PalletId::get().into_sub_account((AccountType::Bonded, id)) } @@ -1589,6 +1647,31 @@ impl Pallet { T::PalletId::get().into_sub_account((AccountType::Reward, id)) } + /// Get the delegator with their associated bonded and reward pool. + fn get_delegator_with_pools( + who: &T::AccountId, + ) -> Result<(Delegator, BondedPool, RewardPool), Error> { + let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + let bonded_pool = + BondedPool::::get(delegator.pool_id).defensive_ok_or(Error::::PoolNotFound)?; + let reward_pool = + RewardPools::::get(delegator.pool_id).defensive_ok_or(Error::::PoolNotFound)?; + Ok((delegator, bonded_pool, reward_pool)) + } + + /// Persist the delegator with their associated bonded and reward pool into storage, consuming + /// all of them. + fn put_delegator_with_pools( + delegator_account: &T::AccountId, + delegator: Delegator, + bonded_pool: BondedPool, + reward_pool: RewardPool, + ) { + bonded_pool.put(); + RewardPools::insert(delegator.pool_id, reward_pool); + Delegators::::insert(delegator_account, delegator); + } + /// Calculate the number of points to issue from a pool as `(current_points / current_balance) * /// new_funds` except for some zero edge cases; see logic and tests for details. fn points_to_issue( @@ -1717,21 +1800,19 @@ impl Pallet { } /// If the delegator has some rewards, transfer a payout from the reward pool to the delegator. - /// - /// # Note - /// This will persist updates for the reward pool to storage. But it will *not* persist updates - /// to the `delegator` or `bonded_pool` to storage, that is the responsibility of the caller. + // Emits events and potentially modifies pool state if any arithmetic saturates, but does + // not persist any of the mutable inputs to storage. fn do_reward_payout( - delegator_account: T::AccountId, + delegator_account: &T::AccountId, delegator: &mut Delegator, bonded_pool: &mut BondedPool, - ) -> DispatchResult { + reward_pool: &mut RewardPool, + ) -> Result, DispatchError> { debug_assert_eq!(delegator.pool_id, bonded_pool.id); - let mut reward_pool = RewardPools::::get(delegator.pool_id) - .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; + let was_destroying = bonded_pool.is_destroying(); let delegator_payout = - Self::calculate_delegator_payout(bonded_pool, &mut reward_pool, delegator)?; + Self::calculate_delegator_payout(bonded_pool, reward_pool, delegator)?; // Transfer payout to the delegator. T::Currency::transfer( @@ -1742,15 +1823,19 @@ impl Pallet { )?; Self::deposit_event(Event::::PaidOut { - delegator: delegator_account, + delegator: delegator_account.clone(), pool_id: delegator.pool_id, payout: delegator_payout, }); - // Write the reward pool to storage - RewardPools::insert(&delegator.pool_id, reward_pool); + if bonded_pool.is_destroying() && !was_destroying { + Self::deposit_event(Event::::StateChanged { + pool_id: delegator.pool_id, + new_state: PoolState::Destroying, + }); + } - Ok(()) + Ok(delegator_payout) } /// Ensure the correctness of the state of this pallet. diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 08ed41e58369b..28b00c3488678 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -219,6 +219,9 @@ impl ExtBuilder { 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 = ::StakingInterface::minimum_bond(); Balances::make_free_balance_be(&10, amount_to_bond * 2); @@ -250,6 +253,22 @@ pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) -> Result<(), }) } +parameter_types! { + static ObservedEvents: usize = 0; +} + +/// All events of this pallet. +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 = ObservedEvents::get(); + ObservedEvents::set(events.len()); + events.into_iter().skip(already_seen).collect() +} + #[cfg(test)] mod test { use super::*; diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index ee3abdb6cb031..8d690540f468a 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -599,6 +599,8 @@ mod join { } mod claim_payout { + use frame_support::storage::with_transaction; + use super::*; fn del(points: Balance, reward_pool_total_earnings: Balance) -> Delegator { @@ -794,38 +796,56 @@ mod claim_payout { #[test] fn do_reward_payout_correctly_sets_pool_state_to_destroying() { ExtBuilder::default().build_and_execute(|| { - let mut bonded_pool = BondedPool::::get(1).unwrap(); - let mut reward_pool = RewardPools::::get(1).unwrap(); - let mut delegator = Delegators::::get(10).unwrap(); + with_transaction(|| { + let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); + let mut delegator = Delegators::::get(10).unwrap(); - // -- reward_pool.total_earnings saturates + // -- reward_pool.total_earnings saturates - // Given - Balances::make_free_balance_be(&default_reward_account(), Balance::MAX); + // Given + Balances::make_free_balance_be(&default_reward_account(), Balance::MAX); - // When - assert_ok!(Pools::do_reward_payout(10, &mut delegator, &mut bonded_pool)); + // When + assert_ok!(Pools::do_reward_payout( + &10, + &mut delegator, + &mut bonded_pool, + &mut reward_pool + )); - // Then - assert!(bonded_pool.is_destroying()); + // Then + assert!(bonded_pool.is_destroying()); + + storage::TransactionOutcome::Rollback(()) + }); // -- current_points saturates (reward_pool.points + new_earnings * bonded_pool.points) + with_transaction(|| { + // Given + let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); + let mut delegator = Delegators::::get(10).unwrap(); + // Force new_earnings * bonded_pool.points == 100 + Balances::make_free_balance_be(&default_reward_account(), 5 + 10); + assert_eq!(bonded_pool.points, 10); + // Force reward_pool.points == U256::MAX - new_earnings * bonded_pool.points + reward_pool.points = U256::MAX - U256::from(100u32); + RewardPools::::insert(1, reward_pool.clone()); - // Given - let mut bonded_pool = BondedPool::::get(1).unwrap(); - let mut delegator = Delegators::::get(10).unwrap(); - // Force new_earnings * bonded_pool.points == 100 - Balances::make_free_balance_be(&default_reward_account(), 5 + 10); - assert_eq!(bonded_pool.points, 10); - // Force reward_pool.points == U256::MAX - new_earnings * bonded_pool.points - reward_pool.points = U256::MAX - U256::from(100u32); - RewardPools::::insert(1, reward_pool.clone()); + // When + assert_ok!(Pools::do_reward_payout( + &10, + &mut delegator, + &mut bonded_pool, + &mut reward_pool + )); - // When - assert_ok!(Pools::do_reward_payout(10, &mut delegator, &mut bonded_pool)); + // Then + assert!(bonded_pool.is_destroying()); - // Then - assert!(bonded_pool.is_destroying()); + storage::TransactionOutcome::Rollback(()) + }) }); } @@ -1158,6 +1178,7 @@ mod claim_payout { .add_delegators(vec![(40, 40), (50, 50)]) .build_and_execute(|| { let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); let ed = Balances::minimum_balance(); // Given the bonded pool has 100 points @@ -1174,39 +1195,48 @@ mod claim_payout { let mut del_50 = Delegators::get(50).unwrap(); // When - assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); + assert_ok!(Pools::do_reward_payout( + &10, + &mut del_10, + &mut bonded_pool, + &mut reward_pool + )); // Then // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool // balance assert_eq!(del_10, del(10, 100)); - assert_eq!( - RewardPools::::get(&1).unwrap(), - rew(90, 100 * 100 - 100 * 10, 100) - ); + assert_eq!(reward_pool, rew(90, 100 * 100 - 100 * 10, 100)); assert_eq!(Balances::free_balance(&10), 10); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 90); // When - assert_ok!(Pools::do_reward_payout(40, &mut del_40, &mut bonded_pool)); + assert_ok!(Pools::do_reward_payout( + &40, + &mut del_40, + &mut bonded_pool, + &mut reward_pool + )); // Then // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance assert_eq!(del_40, del(40, 100)); - assert_eq!( - RewardPools::::get(&1).unwrap(), - rew(50, 9_000 - 100 * 40, 100) - ); + assert_eq!(reward_pool, rew(50, 9_000 - 100 * 40, 100)); assert_eq!(Balances::free_balance(&40), 40); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 50); // When - assert_ok!(Pools::do_reward_payout(50, &mut del_50, &mut bonded_pool)); + assert_ok!(Pools::do_reward_payout( + &50, + &mut del_50, + &mut bonded_pool, + &mut reward_pool + )); // Then // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance assert_eq!(del_50, del(50, 100)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 100)); + assert_eq!(reward_pool, rew(0, 0, 100)); assert_eq!(Balances::free_balance(&50), 50); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); @@ -1214,23 +1244,33 @@ mod claim_payout { Balances::make_free_balance_be(&default_reward_account(), ed + 50); // When - assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); + assert_ok!(Pools::do_reward_payout( + &10, + &mut del_10, + &mut bonded_pool, + &mut reward_pool + )); // Then // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance assert_eq!(del_10, del(10, 150)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(45, 5_000 - 50 * 10, 150)); + assert_eq!(reward_pool, rew(45, 5_000 - 50 * 10, 150)); assert_eq!(Balances::free_balance(&10), 10 + 5); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 45); // When - assert_ok!(Pools::do_reward_payout(40, &mut del_40, &mut bonded_pool)); + assert_ok!(Pools::do_reward_payout( + &40, + &mut del_40, + &mut bonded_pool, + &mut reward_pool + )); // Then // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool // balance assert_eq!(del_40, del(40, 150)); - assert_eq!(RewardPools::::get(1).unwrap(), rew(25, 4_500 - 50 * 40, 150)); + assert_eq!(reward_pool, rew(25, 4_500 - 50 * 40, 150)); assert_eq!(Balances::free_balance(&40), 40 + 20); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); @@ -1239,14 +1279,19 @@ mod claim_payout { assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75); // When - assert_ok!(Pools::do_reward_payout(50, &mut del_50, &mut bonded_pool)); + assert_ok!(Pools::do_reward_payout( + &50, + &mut del_50, + &mut bonded_pool, + &mut reward_pool + )); // Then // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 // pool balance assert_eq!(del_50, del(50, 200)); assert_eq!( - RewardPools::::get(1).unwrap(), + reward_pool, rew( 25, // old pool points + points from new earnings - del points. @@ -1261,12 +1306,17 @@ mod claim_payout { assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); // When - assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); + assert_ok!(Pools::do_reward_payout( + &10, + &mut del_10, + &mut bonded_pool, + &mut reward_pool + )); // Then // We expect a payout of 5 assert_eq!(del_10, del(10, 200)); - assert_eq!(RewardPools::::get(1).unwrap(), rew(20, 2_500 - 10 * 50, 200)); + assert_eq!(reward_pool, rew(20, 2_500 - 10 * 50, 200)); assert_eq!(Balances::free_balance(&10), 15 + 5); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 20); @@ -1275,13 +1325,18 @@ mod claim_payout { assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420); // When - assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); + assert_ok!(Pools::do_reward_payout( + &10, + &mut del_10, + &mut bonded_pool, + &mut reward_pool + )); // Then // We expect a payout of 40 assert_eq!(del_10, del(10, 600)); assert_eq!( - RewardPools::::get(1).unwrap(), + reward_pool, rew( 380, // old pool points + points from new earnings - del points @@ -1300,40 +1355,49 @@ mod claim_payout { assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400); // When - assert_ok!(Pools::do_reward_payout(10, &mut del_10, &mut bonded_pool)); + assert_ok!(Pools::do_reward_payout( + &10, + &mut del_10, + &mut bonded_pool, + &mut reward_pool + )); // Then // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool // balance assert_eq!(del_10, del(10, 620)); - assert_eq!( - RewardPools::::get(1).unwrap(), - rew(398, (38_000 + 20 * 100) - 10 * 20, 620) - ); + assert_eq!(reward_pool, rew(398, (38_000 + 20 * 100) - 10 * 20, 620)); assert_eq!(Balances::free_balance(&10), 60 + 2); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 398); // When - assert_ok!(Pools::do_reward_payout(40, &mut del_40, &mut bonded_pool)); + assert_ok!(Pools::do_reward_payout( + &40, + &mut del_40, + &mut bonded_pool, + &mut reward_pool + )); // Then // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 // pool balance assert_eq!(del_40, del(40, 620)); - assert_eq!( - RewardPools::::get(1).unwrap(), - rew(210, 39_800 - 40 * 470, 620) - ); + assert_eq!(reward_pool, rew(210, 39_800 - 40 * 470, 620)); assert_eq!(Balances::free_balance(&40), 60 + 188); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 210); // When - assert_ok!(Pools::do_reward_payout(50, &mut del_50, &mut bonded_pool)); + assert_ok!(Pools::do_reward_payout( + &50, + &mut del_50, + &mut bonded_pool, + &mut reward_pool + )); // Then // Expect payout of 210: (21,000 / 21,000) * 210 assert_eq!(del_50, del(50, 620)); - assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 21_000 - 50 * 420, 620)); + assert_eq!(reward_pool, rew(0, 21_000 - 50 * 420, 620)); assert_eq!(Balances::free_balance(&50), 100 + 210); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); }); @@ -2453,3 +2517,139 @@ mod set_configs { }); } } + +mod bond_extra { + use super::*; + use crate::Event; + + #[test] + fn bond_extra_from_free_balance_creator() { + ExtBuilder::default().build_and_execute(|| { + // 10 is the owner and a delegator in pool 1, give them some more funds. + Balances::make_free_balance_be(&10, 100); + + // given + assert_eq!(Delegators::::get(10).unwrap().points, 10); + assert_eq!(BondedPools::::get(1).unwrap().points, 10); + assert_eq!(Balances::free_balance(10), 100); + + // when + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10))); + + // then + assert_eq!(Balances::free_balance(10), 90); + assert_eq!(Delegators::::get(10).unwrap().points, 20); + assert_eq!(BondedPools::::get(1).unwrap().points, 20); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { delegator: 10, pool_id: 1, bonded: 10, joined: false } + ] + ); + + // when + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(20))); + + // then + assert_eq!(Balances::free_balance(10), 70); + assert_eq!(Delegators::::get(10).unwrap().points, 40); + assert_eq!(BondedPools::::get(1).unwrap().points, 40); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::Bonded { delegator: 10, pool_id: 1, bonded: 20, joined: false }] + ); + }) + } + + #[test] + fn bond_extra_from_rewards_creator() { + ExtBuilder::default().build_and_execute(|| { + // put some money in the reward account, all of which will belong to 10 as the only + // delegator of the pool. + Balances::make_free_balance_be(&default_reward_account(), 7); + // ... if which only 2 is claimable to make sure the reward account does not die. + let claimable_reward = 7 - ExistentialDeposit::get(); + + // given + assert_eq!(Delegators::::get(10).unwrap().points, 10); + assert_eq!(BondedPools::::get(1).unwrap().points, 10); + assert_eq!(Balances::free_balance(10), 5); + + // when + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::Rewards)); + + // then + assert_eq!(Balances::free_balance(10), 5); + assert_eq!(Delegators::::get(10).unwrap().points, 10 + claimable_reward); + assert_eq!(BondedPools::::get(1).unwrap().points, 10 + claimable_reward); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::PaidOut { delegator: 10, pool_id: 1, payout: claimable_reward }, + Event::Bonded { + delegator: 10, + pool_id: 1, + bonded: claimable_reward, + joined: false + } + ] + ); + }) + } + + #[test] + fn bond_extra_from_rewards_joiner() { + ExtBuilder::default().add_delegators(vec![(20, 20)]).build_and_execute(|| { + // put some money in the reward account, all of which will belong to 10 as the only + // delegator of the pool. + Balances::make_free_balance_be(&default_reward_account(), 8); + // ... if which only 3 is claimable to make sure the reward account does not die. + let claimable_reward = 8 - ExistentialDeposit::get(); + // NOTE: easier to read of we use 3, so let's use the number instead of variable. + assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3"); + + // given + assert_eq!(Delegators::::get(10).unwrap().points, 10); + assert_eq!(Delegators::::get(20).unwrap().points, 20); + assert_eq!(BondedPools::::get(1).unwrap().points, 30); + assert_eq!(Balances::free_balance(10), 5); + assert_eq!(Balances::free_balance(20), 20); + + // when + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::Rewards)); + + // then + assert_eq!(Balances::free_balance(10), 5); + // 10's share of the reward is 1/3, since they gave 10/30 of the total shares. + assert_eq!(Delegators::::get(10).unwrap().points, 10 + 1); + assert_eq!(BondedPools::::get(1).unwrap().points, 30 + 1); + + // when + assert_ok!(Pools::bond_extra(Origin::signed(20), BondExtra::Rewards)); + + // then + assert_eq!(Balances::free_balance(20), 20); + // 20's share of the rewards is the other 2/3 of the rewards, since they have 20/30 of + // the shares + assert_eq!(Delegators::::get(20).unwrap().points, 20 + 2); + assert_eq!(BondedPools::::get(1).unwrap().points, 30 + 3); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { delegator: 20, pool_id: 1, bonded: 20, joined: true }, + Event::PaidOut { delegator: 10, pool_id: 1, payout: 1 }, + Event::Bonded { delegator: 10, pool_id: 1, bonded: 1, joined: false }, + Event::PaidOut { delegator: 20, pool_id: 1, payout: 2 }, + Event::Bonded { delegator: 20, pool_id: 1, bonded: 2, joined: false } + ] + ); + }) + } +} diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index 8c61874003bce..7af08f9d703e7 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -129,9 +129,13 @@ pub trait DefensiveOption { /// if `None`, which should never happen. fn defensive_map_or_else U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U; - /// Defensively transform this option to a result. + /// Defensively transform this option to a result, mapping `None` to the return value of an + /// error closure. fn defensive_ok_or_else E>(self, err: F) -> Result; + /// Defensively transform this option to a result, mapping `None` to a default value. + fn defensive_ok_or(self, err: E) -> Result; + /// Exactly the same as `map`, but it prints the appropriate warnings if the value being mapped /// is `None`. fn defensive_map U>(self, f: F) -> Option; @@ -284,6 +288,13 @@ impl DefensiveOption for Option { }) } + fn defensive_ok_or(self, err: E) -> Result { + self.ok_or_else(|| { + defensive!(); + err + }) + } + fn defensive_map U>(self, f: F) -> Option { match self { Some(inner) => Some(f(inner)), From eb943ed333f0fc5387798dd4bf288e3bd8cbf78a Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 29 Mar 2022 09:44:32 -0700 Subject: [PATCH 223/299] FMT --- frame/nomination-pools/src/tests.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index e7b20d966fb1b..49f04fe9c872c 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -74,7 +74,8 @@ fn exercise_pool_life_cycle() { // assert_ok!(Pools::join(Origin::signed(12), MinJoinBond::::get(), 2)); // assert_eq!(Balances::free_balance(&12), ed); - // assert_eq!(Balances::free_balance(&bonded_pool_account), min_create_bond + min_join_bond); + // assert_eq!(Balances::free_balance(&bonded_pool_account), min_create_bond + + // min_join_bond); // // The pools gets some rewards // assert_ok!(Balances::mutate_account(&reward_account, |a| a.free += 50)); From 07a6a67e2afde219c5e7001cc4e7e0996c588182 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Mon, 4 Apr 2022 20:40:15 -0400 Subject: [PATCH 224/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index ad9e4ffce35aa..28bf7fc2791fe 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -61,7 +61,7 @@ //! user must call [`Call::nominate`] to start nominating. [`Call::nominate`] can be called at //! anytime to update validator selection. //! -//! To help facilitate pool adminstration the pool has one of three states (see [`PoolState`]): +//! To help facilitate pool administration the pool has one of three states (see [`PoolState`]): //! //! * Open: Anyone can join the pool and no delegators can be permissionlessly removed. //! * Blocked: No delegators can join and some admin roles can kick delegators. From c194f47293205bc9201fdf8fc50a6c85c474581f Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Mon, 4 Apr 2022 20:40:22 -0400 Subject: [PATCH 225/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 28bf7fc2791fe..558214c700fc0 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -55,7 +55,7 @@ //! //! For design docs see the [slashing](#slashing) section. //! -//! ### Adminstration +//! ### Administration //! //! A pool can be created with the [`Call::create`] call. Once created, the pools nominator or root //! user must call [`Call::nominate`] to start nominating. [`Call::nominate`] can be called at From ea27a8ad3a5f9ecfa8415c4b106e4a34913e88d2 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Mon, 4 Apr 2022 20:40:31 -0400 Subject: [PATCH 226/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 558214c700fc0..c2762ffa8ce11 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -237,7 +237,7 @@ //! //! This scheme "averages" out the points value in the unbonded pool. //! -//! Once a delgators `unbonding_era` is older than `current_era - +//! Once a delegators `unbonding_era` is older than `current_era - //! [sp_staking::StakingInterface::bonding_duration]`, it can can cash it's points out of the //! corresponding unbonding pool. If it's `unbonding_era` is older than `current_era - //! TotalUnbondingPools`, it can cash it's points from the unbonded pool. From 68ef85870b2ff8cbaccdd3b360f38ac3a9501f2e Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Mon, 4 Apr 2022 20:40:44 -0400 Subject: [PATCH 227/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index c2762ffa8ce11..72baac815ed3d 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1203,7 +1203,7 @@ pub mod pallet { /// * The pool is destroying, the delegator is the depositor and no other delegators are in /// the pool. /// - /// # Conditions for permissioned dispatch (i.e. the caller is also the target): + /// # Conditions for permissioned dispatch (i.e. the caller is also the `delegator_account`): /// /// * The caller is not the depositor. /// * The caller is the depositor, the pool is destroying and no other delegators are in the From b4edcb343b92142e4d284b662902a1da5ef005d3 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 5 Apr 2022 09:03:25 +0200 Subject: [PATCH 228/299] make it compile end to end --- bin/node/testing/src/genesis.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/node/testing/src/genesis.rs b/bin/node/testing/src/genesis.rs index 8d2b53b0b7210..73e7ea50261d3 100644 --- a/bin/node/testing/src/genesis.rs +++ b/bin/node/testing/src/genesis.rs @@ -92,5 +92,6 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> Gen gilt: Default::default(), transaction_storage: Default::default(), transaction_payment: Default::default(), + nomination_pools: Default::default(), } } From e64f379240ffc8cce4b0bb4672f2fd2e42cf89d2 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:39:09 -0400 Subject: [PATCH 229/299] Update some type viz --- frame/nomination-pools/src/lib.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 72baac815ed3d..4c17ad68528ea 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -312,12 +312,16 @@ pub mod weights; pub use pallet::*; pub use weights::WeightInfo; +/// The balance type used by the currency system. pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -pub type SubPoolsWithEra = BoundedBTreeMap, TotalUnbondingPools>; +/// Type used to track the points of a reward pool. pub type RewardPoints = U256; +/// Type used for unique identifier of each pool. pub type PoolId = u32; +type SubPoolsWithEra = BoundedBTreeMap, TotalUnbondingPools>; + const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; /// Possible operations on the configuration values of this pallet. @@ -331,7 +335,7 @@ pub enum ConfigOp { Remove, } -/// The type of binding that can happen to a pool. +/// The type of bo``nding that can happen to a pool. enum BondType { /// Someone is bonding into the pool upon creation. Create, @@ -1178,6 +1182,7 @@ pub mod pallet { /// The delegator will earn rewards pro rata based on the delegators stake vs the sum of the /// delegators in the pools stake. Rewards do not "expire". #[pallet::weight(T::WeightInfo::claim_payout())] + #[transactional] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let (mut delegator, mut bonded_pool, mut reward_pool) = @@ -1212,7 +1217,7 @@ pub mod pallet { /// Note: If there are too many unlocking chunks to unbond with the pool account, /// [`Self::withdraw_unbonded_pool`] can be called to try and minimize unlocking chunks. #[pallet::weight(T::WeightInfo::unbond_other())] - #[frame_support::transactional] + #[transactional] pub fn unbond_other( origin: OriginFor, delegator_account: T::AccountId, From 9d613cc11a9db7b8297c96fd9b746eb4651a94ca Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:48:00 -0400 Subject: [PATCH 230/299] Update kick terminology --- frame/nomination-pools/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 4c17ad68528ea..cca9d4b084d84 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -18,6 +18,7 @@ //! * delegators: Accounts that are members of pools. See [`Delegator`] and [`Delegators`]. //! Delegators are identified via their account. //! * point: A unit of measure for a delegators portion of a pool's funds. +//! * kick: The act of a pool administrator forcibly ejecting a delegator. //! //! ## Usage //! From 3db567b5cf8a6e043f0acca4aef330696ce41ea6 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Tue, 5 Apr 2022 15:48:12 -0400 Subject: [PATCH 231/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 4c17ad68528ea..1b82de1f59bf8 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -619,7 +619,7 @@ impl BondedPool { // they are the delegator in the pool. Note that an invariant is once the pool is // destroying it cannot switch states, so by being in destroying we are guaranteed no // other delegators can possibly join. - (false, true) | (true, true) => { + (_, true) => { ensure!(target_delegator.points == self.points, Error::::NotOnlyDelegator); ensure!(self.is_destroying(), Error::::NotDestroying); }, From a8c6a1e717fa0952d35f598930ccf2bcbeee5a8c Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Tue, 5 Apr 2022 15:52:19 -0400 Subject: [PATCH 232/299] Update frame/nomination-pools/src/lib.rs --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index d4b70cb41db29..a6cbee8e4a678 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -405,7 +405,7 @@ pub struct PoolRoles { pub root: AccountId, /// Can select which validators the pool nominates. pub nominator: AccountId, - /// can change the pools state and kick delegators if the pool is blocked. + /// Can change the pools state and kick delegators if the pool is blocked. pub state_toggler: AccountId, } From 6b4cea3560bc97c75b1336125b88255bb256810e Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 6 Apr 2022 12:28:54 -0400 Subject: [PATCH 233/299] Cache bonded account when creating pool --- frame/nomination-pools/src/lib.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index d4b70cb41db29..429e017eabd94 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -628,7 +628,8 @@ impl BondedPool { Ok(()) } - /// Returns a result indicating if [`Pallet::withdraw_unbonded_other`] can be executed. + /// Returns Ok if [`Pallet::withdraw_unbonded_other`] can be executed, otherwise returns a + /// dispatch error. fn ok_to_withdraw_unbonded_other_with( &self, caller: &T::AccountId, @@ -686,9 +687,11 @@ impl BondedPool { amount: BalanceOf, ty: BondType, ) -> Result, DispatchError> { + // Cache the value + let bonded_account = self.bonded_account(); T::Currency::transfer( &who, - &self.bonded_account(), + &bonded_account, amount, match ty { BondType::Create => ExistenceRequirement::AllowDeath, @@ -701,15 +704,15 @@ impl BondedPool { match ty { BondType::Create => T::StakingInterface::bond( - self.bonded_account(), - self.bonded_account(), + bonded_account.clone(), + bonded_account, amount, self.reward_account(), )?, // The pool should always be created in such a way its in a state to bond extra, but if // the active balance is slashed below the minimum bonded or the account cannot be // found, we exit early. - BondType::Later => T::StakingInterface::bond_extra(self.bonded_account(), amount)?, + BondType::Later => T::StakingInterface::bond_extra(bonded_account, amount)?, } Ok(points_issued) @@ -1091,7 +1094,7 @@ pub mod pallet { /// `existential deposit + amount` in their account. /// * Only a pool with [`PoolState::Open`] can be joined #[pallet::weight(T::WeightInfo::join())] - #[frame_support::transactional] + #[transactional] pub fn join(origin: OriginFor, amount: BalanceOf, pool_id: PoolId) -> DispatchResult { let who = ensure_signed(origin)?; @@ -1292,6 +1295,7 @@ pub mod pallet { /// This is useful if their are too many unlocking chunks to call `unbond_other`, and some /// can be cleared by withdrawing. #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))] + #[transactional] pub fn pool_withdraw_unbonded( origin: OriginFor, pool_id: PoolId, @@ -1325,6 +1329,7 @@ pub mod pallet { #[pallet::weight( T::WeightInfo::withdraw_unbonded_other_kill(*num_slashing_spans) )] + #[transactional] pub fn withdraw_unbonded_other( origin: OriginFor, delegator_account: T::AccountId, @@ -1448,7 +1453,7 @@ pub mod pallet { /// In addition to `amount`, the caller will transfer the existential deposit; so the caller /// needs at have at least `amount + existential_deposit` transferrable. #[pallet::weight(T::WeightInfo::create())] - #[frame_support::transactional] + #[transactional] pub fn create( origin: OriginFor, amount: BalanceOf, From 4546e447f92f03359992f851f302da55b0fac4ec Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 6 Apr 2022 13:08:34 -0400 Subject: [PATCH 234/299] Add bond extra weight stuff --- frame/nomination-pools/src/lib.rs | 9 ++++++--- frame/nomination-pools/src/weights.rs | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 9a2104fb4f7d0..37c0b814d2273 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1105,8 +1105,8 @@ pub mod pallet { let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; bonded_pool.ok_to_join()?; - // don't actually care about writing the reward pool, we just need its total earnings at - // this point in time. + // We just need its total earnings at this point in time, but we don't need to write it + // because we are not adjusting its points (all other values can calculated virtual). let reward_pool = RewardPool::::get_and_update(pool_id) .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; @@ -1146,7 +1146,10 @@ pub mod pallet { // NOTE: this transaction is implemented with the sole purpose of readability and // correctness, not optimization. We read/write several storage items multiple times instead // of just once, in the spirit reusing code. - #[pallet::weight(0)] + #[pallet::weight( + T::WeightInfo::bond_extra_transfer() + .max(T::WeightInfo::bond_extra_reward()) + )] #[transactional] pub fn bond_extra(origin: OriginFor, extra: BondExtra>) -> DispatchResult { let who = ensure_signed(origin)?; diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index 4468b39caacf9..fc60e583ea9d4 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -44,6 +44,8 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_nomination_pools. pub trait WeightInfo { fn join() -> Weight; + fn bond_extra_transfer() -> Weight; + fn bond_extra_reward() -> Weight; fn claim_payout() -> Weight; fn unbond_other() -> Weight; fn pool_withdraw_unbonded(s: u32, ) -> Weight; @@ -75,6 +77,16 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(15 as Weight)) .saturating_add(T::DbWeight::get().writes(11 as Weight)) } + fn bond_extra_transfer() -> Weight { + (109_762_000 as Weight) + .saturating_add(T::DbWeight::get().reads(15 as Weight)) + .saturating_add(T::DbWeight::get().writes(11 as Weight)) + } + fn bond_extra_reward() -> Weight { + (109_762_000 as Weight) + .saturating_add(T::DbWeight::get().reads(15 as Weight)) + .saturating_add(T::DbWeight::get().writes(11 as Weight)) + } // Storage: NominationPools Delegators (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:0) // Storage: NominationPools RewardPools (r:1 w:1) @@ -210,6 +222,16 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(15 as Weight)) .saturating_add(RocksDbWeight::get().writes(11 as Weight)) } + fn bond_extra_reward() -> Weight { + (109_762_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(15 as Weight)) + .saturating_add(RocksDbWeight::get().writes(11 as Weight)) + } + fn bond_extra_transfer() -> Weight { + (109_762_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(15 as Weight)) + .saturating_add(RocksDbWeight::get().writes(11 as Weight)) + } // Storage: NominationPools Delegators (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:0) // Storage: NominationPools RewardPools (r:1 w:1) From 0dbb34869571a68ce993b7b55993add0d1e63158 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Wed, 6 Apr 2022 13:16:49 -0400 Subject: [PATCH 235/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 37c0b814d2273..f5cb0d2009ec6 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1184,7 +1184,7 @@ pub mod pallet { /// A bonded delegator can use this to claim their payout based on the rewards that the pool /// has accumulated since their last claimed payout (OR since joining if this is there first - /// time claiming rewards). The payout will be transffered to the delegator's account. + /// time claiming rewards). The payout will be transferred to the delegator's account. /// /// The delegator will earn rewards pro rata based on the delegators stake vs the sum of the /// delegators in the pools stake. Rewards do not "expire". From 2855e73d244731b86fadf7c62aaabc88f36c8a4a Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 6 Apr 2022 13:33:39 -0400 Subject: [PATCH 236/299] Update docs for pool withdraw unbonded --- frame/nomination-pools/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index f5cb0d2009ec6..792e67a1fb724 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1215,7 +1215,8 @@ pub mod pallet { /// * The pool is destroying, the delegator is the depositor and no other delegators are in /// the pool. /// - /// # Conditions for permissioned dispatch (i.e. the caller is also the `delegator_account`): + /// # Conditions for permissioned dispatch (i.e. the caller is also the + /// `delegator_account`): /// /// * The caller is not the depositor. /// * The caller is the depositor, the pool is destroying and no other delegators are in the @@ -1296,7 +1297,9 @@ pub mod pallet { /// Call `withdraw_unbonded` for the pools account. This call can be made by any account. /// /// This is useful if their are too many unlocking chunks to call `unbond_other`, and some - /// can be cleared by withdrawing. + /// can be cleared by withdrawing. In the case there are too many unlocking chunks, the user + /// would probably see an error like `NoMoreChunks` emitted from the staking system when + /// they attempt to unbond. #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))] #[transactional] pub fn pool_withdraw_unbonded( From cd03699793bca9b7f63ef8b8af11dee0956b034a Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 6 Apr 2022 13:40:09 -0400 Subject: [PATCH 237/299] Update docs for unbond --- frame/nomination-pools/src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 792e67a1fb724..dd2e703a7bd39 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -336,7 +336,7 @@ pub enum ConfigOp { Remove, } -/// The type of bo``nding that can happen to a pool. +/// The type of bonding that can happen to a pool. enum BondType { /// Someone is bonding into the pool upon creation. Create, @@ -1222,8 +1222,12 @@ pub mod pallet { /// * The caller is the depositor, the pool is destroying and no other delegators are in the /// pool. /// - /// Note: If there are too many unlocking chunks to unbond with the pool account, - /// [`Self::withdraw_unbonded_pool`] can be called to try and minimize unlocking chunks. + /// # Note + /// + /// If there are too many unlocking chunks to unbond with the pool account, + /// [`Self::withdraw_unbonded_pool`] can be called to try and minimize unlocking chunks. If + /// there are too many unlocking chunks, the result of this call will likely be the + /// `NoMoreChunks` error from the staking system. #[pallet::weight(T::WeightInfo::unbond_other())] #[transactional] pub fn unbond_other( From 1a466196d1c2f8c4caf63c94dcd88c7bb12b495e Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 6 Apr 2022 14:17:21 -0400 Subject: [PATCH 238/299] Improve Doc --- frame/nomination-pools/src/lib.rs | 4 +-- frame/nomination-pools/src/tests.rs | 43 ----------------------------- 2 files changed, 2 insertions(+), 45 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index dd2e703a7bd39..450bf8e061827 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -130,8 +130,8 @@ //! //! ### Reward pool //! -//! When a pool is first bonded it sets up an arbitrary account as its reward destination. To track -//! staking rewards we track how the balance of this reward account changes. +//! When a pool is first bonded it sets up an deterministic, inaccessible account as its reward +//! destination. To track staking rewards we track how the balance of this reward account changes. //! //! The reward pool needs to store: //! diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 49f04fe9c872c..4317d4eec4020 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -51,49 +51,6 @@ fn test_setup_works() { }) } -#[test] -fn exercise_pool_life_cycle() { - ExtBuilder::default().build_and_execute(|| { - // let bonded_pool_account = Pools::create_bonded_account(2); - // let reward_account = Pools::create_reward_account(2); - // let ed = Balances::minimum_balance(); - // let min_create_bond = MinCreateBond::::get().max(StakingMock::minimum_bond()); - // let min_join_bond = MinJoinBond::::get(); - - // // Create a pool - // Balances::make_free_balance_be(&11, ed + min_create_bond); - // assert_ok!(Pools::create(Origin::signed(11), min_create_bond, 101, 102, 103,)); - - // assert_eq!(Balances::free_balance(&11), 0); - // assert_eq!(Balances::free_balance(&reward_account), ed); - // assert_eq!(Balances::free_balance(&bonded_pool_account), min_create_bond); - - // // A delegator joins the pool - // Balances::make_free_balance_be(&12, ed + min_join_bond); - - // assert_ok!(Pools::join(Origin::signed(12), MinJoinBond::::get(), 2)); - - // assert_eq!(Balances::free_balance(&12), ed); - // assert_eq!(Balances::free_balance(&bonded_pool_account), min_create_bond + - // min_join_bond); - - // // The pools gets some rewards - // assert_ok!(Balances::mutate_account(&reward_account, |a| a.free += 50)); - - // let reward_for_11 = 50 * min_create_bond / (min_join_bond + min_create_bond); - // let reward_for_12 = 50 * min_join_bond / (min_join_bond + min_create_bond); - - // assert_ok!(Pools::claim_payout(Origin::signed(11))); - // assert_ok!(Pools::claim_payout(Origin::signed(12))); - - // // The rewards are slightly lossy - // assert_eq!(Balances::free_balance(&11), ed + reward_for_11 - 5); - // assert_eq!(Balances::free_balance(&12), ed + reward_for_12 + 1); - - // WIP - }); -} - mod bonded_pool { use super::*; #[test] From 7439442b58ccad007a7d9b775b05580cae8ec41e Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Wed, 6 Apr 2022 19:07:24 -0400 Subject: [PATCH 239/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 450bf8e061827..8fc4bcbf4e644 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -42,7 +42,7 @@ //! unbonding process by unbonding all of the delegators funds. //! //! Second, once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the delegator -//! can call [`Call::withdraw_unbonded_other`] to withdraw all there funds. +//! can call [`Call::withdraw_unbonded_other`] to withdraw all their funds. //! //! For design docs see the [bonded pool](#bonded-pool) and [unbonding sub //! pools](#unbonding-sub-pools) sections. From 78a4bb06d66423995469d8850f66ce2722377ce9 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Wed, 6 Apr 2022 19:07:36 -0400 Subject: [PATCH 240/299] Update frame/nomination-pools/Cargo.toml Co-authored-by: Oliver Tale-Yazdi --- frame/nomination-pools/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 153b988facee9..6565e695901e2 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -description = "FRAME pools pallet" +description = "FRAME nomination pools pallet" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] From 3e6c8d3bd30b740449149f67ff54544c8d00da9d Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Wed, 6 Apr 2022 19:08:14 -0400 Subject: [PATCH 241/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 8fc4bcbf4e644..ea0734e28c507 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -67,7 +67,7 @@ //! * Open: Anyone can join the pool and no delegators can be permissionlessly removed. //! * Blocked: No delegators can join and some admin roles can kick delegators. //! * Destroying: No delegators can join and all delegators can be permissionlessly removed with -//! [`Call::unbond_other`] and [`Call::withdraw_unbonded_other`]. Once a pool is destroying state, +//! [`Call::unbond_other`] and [`Call::withdraw_unbonded_other`]. Once a pool is in destroying state, //! it cannot be reverted to another state. //! //! A pool has 3 administrative roles (see [`PoolRoles`]): From e44ca53fc13edf1127acfe22459f02c0f3c30792 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Wed, 6 Apr 2022 19:12:28 -0400 Subject: [PATCH 242/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index ea0734e28c507..2691406193e21 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -256,7 +256,7 @@ //! //! Unbonding pools need to be slashed to ensure all nominators whom where in the bonded pool //! while it was backing a validator that equivocated are punished. Without these measures a -//! nominator could unbond right after a validator equivocated with no consequences. +//! delegator could unbond right after a validator equivocated with no consequences. //! //! This strategy is unfair to delegators who joined after the slash, because they get slashed as //! well, but spares delegators who unbond. The latter is much more important for security: if a From 3fc8c79308ad14e5615b13f9f545a0c949f8d79b Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Wed, 6 Apr 2022 19:12:50 -0400 Subject: [PATCH 243/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 2691406193e21..8dc83c9fcb3f1 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -72,7 +72,7 @@ //! //! A pool has 3 administrative roles (see [`PoolRoles`]): //! -//! * Depositor: creates the pool and is the initial delegator. They can only leave pool once all +//! * Depositor: creates the pool and is the initial delegator. They can only leave the pool once all //! other delegators have left. Once they fully leave the pool is destroyed. //! * Nominator: can select which validators the pool nominates. //! * State-Toggler: can change the pools state and kick delegators if the pool is blocked. From cfeaa136e4c2522158c19fd83983486a2db66cc2 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 7 Apr 2022 12:12:33 -0400 Subject: [PATCH 244/299] Improve Docs --- frame/nomination-pools/src/lib.rs | 21 ++++++++++++++++++++- frame/nomination-pools/src/tests.rs | 17 +++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 8dc83c9fcb3f1..22f6e9079c962 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1,7 +1,26 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-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. + //! # Nomination Pools for Staking Delegation //! //! A pallet that allows delegators to delegate their stake to nominating pools. A nomination pool -//! acts as nominator and nominates validators on the delegators behalf. # Index +//! acts as nominator and nominates validators on the delegators behalf. +//! +//! # Index //! //! * [Key terms](#key-terms) //! * [Usage](#usage) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 4317d4eec4020..949a9e88e9abe 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-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 super::*; use crate::mock::*; use frame_support::{assert_noop, assert_ok, assert_storage_noop}; From a6b458790a88c6e4fedabbe1daf16a90af036fde Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 7 Apr 2022 15:45:32 -0400 Subject: [PATCH 245/299] Some docs improvements --- frame/nomination-pools/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 22f6e9079c962..9a3b86471de76 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -45,8 +45,6 @@ //! //! A account can stake funds with a nomination pool by calling [`Call::join`]. //! -//! For design docs see the [joining](#joining) section. -//! //! ### Claim rewards //! //! After joining a pool, a delegator can claim rewards by calling [`Call::claim_payout`]. From bd8ceaf4dbcbc5d7f1cdd4450c047a1e3def56dc Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 7 Apr 2022 15:46:51 -0400 Subject: [PATCH 246/299] fmt --- frame/nomination-pools/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 9a3b86471de76..2eac738721280 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -84,13 +84,13 @@ //! * Open: Anyone can join the pool and no delegators can be permissionlessly removed. //! * Blocked: No delegators can join and some admin roles can kick delegators. //! * Destroying: No delegators can join and all delegators can be permissionlessly removed with -//! [`Call::unbond_other`] and [`Call::withdraw_unbonded_other`]. Once a pool is in destroying state, -//! it cannot be reverted to another state. +//! [`Call::unbond_other`] and [`Call::withdraw_unbonded_other`]. Once a pool is in destroying +//! state, it cannot be reverted to another state. //! //! A pool has 3 administrative roles (see [`PoolRoles`]): //! -//! * Depositor: creates the pool and is the initial delegator. They can only leave the pool once all -//! other delegators have left. Once they fully leave the pool is destroyed. +//! * Depositor: creates the pool and is the initial delegator. They can only leave the pool once +//! all other delegators have left. Once they fully leave the pool is destroyed. //! * Nominator: can select which validators the pool nominates. //! * State-Toggler: can change the pools state and kick delegators if the pool is blocked. //! * Root: can change the nominator, state-toggler, or itself and can perform any of the actions From 3ba68c7ecd75c4c98415d182644012afe4fcad9b Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 7 Apr 2022 18:20:06 -0400 Subject: [PATCH 247/299] Remove unlock_era --- frame/staking/src/benchmarking.rs | 4 +-- frame/staking/src/lib.rs | 8 +++--- frame/staking/src/pallet/mod.rs | 4 +-- frame/staking/src/tests.rs | 44 +++++++++++++++---------------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 28dae6e657748..7456c16190113 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -644,7 +644,7 @@ benchmarks! { assert!(value * l.into() + origin_weight <= dest_weight); let unlock_chunk = UnlockChunk::> { value, - unlock_era: EraIndex::zero(), + era: EraIndex::zero(), }; let stash = scenario.origin_stash1.clone(); @@ -793,7 +793,7 @@ benchmarks! { let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); let unlock_chunk = UnlockChunk::> { value: 1u32.into(), - unlock_era: EraIndex::zero(), + era: EraIndex::zero(), }; for _ in 0 .. l { staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap(); diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 18a8b72a9c70e..c3a8823a60639 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -434,7 +434,7 @@ pub struct UnlockChunk { value: Balance, /// Era number at which point it'll be unlocked. #[codec(compact)] - unlock_era: EraIndex, + era: EraIndex, } /// The ledger of a (bonded) stash. @@ -480,7 +480,7 @@ impl StakingLedger { .unlocking .into_iter() .filter(|chunk| { - if chunk.unlock_era > current_era { + if chunk.era > current_era { true } else { total = total.saturating_sub(chunk.value); @@ -558,7 +558,7 @@ impl StakingLedger { // Calculate the total balance of active funds and unlocking funds in the affected range. let (affected_balance, slash_chunks_priority): (_, Box>) = { if let Some(start_index) = - self.unlocking.iter().position(|c| c.unlock_era >= chunk_unlock_era_after_slash) + self.unlocking.iter().position(|c| c.era >= chunk_unlock_era_after_slash) { // The indices of the first chunk after the slash up through the most recent chunk. // (The most recent chunk is at greatest from this era) @@ -607,7 +607,7 @@ impl StakingLedger { if let Some(chunk) = self.unlocking.get_mut(i).defensive() { slash_out_of(&mut chunk.value, &mut remaining_slash); // write the new slashed value of this chunk to the map. - slashed_unlocking.insert(chunk.unlock_era, chunk.value); + slashed_unlocking.insert(chunk.era, chunk.value); if remaining_slash.is_zero() { break } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 26a51b1c8c807..1984e01338b4a 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -921,7 +921,7 @@ pub mod pallet { // Note: in case there is no current era it is fine to bond one era more. let era = Self::current_era().unwrap_or(0) + T::BondingDuration::get(); if let Some(mut chunk) = - ledger.unlocking.last_mut().filter(|chunk| chunk.unlock_era == era) + ledger.unlocking.last_mut().filter(|chunk| chunk.era == era) { // To keep the chunk count down, we only keep one chunk per era. Since // `unlocking` is a FiFo queue, if a chunk exists for `era` we know that it will @@ -930,7 +930,7 @@ pub mod pallet { } else { ledger .unlocking - .try_push(UnlockChunk { value, unlock_era: era }) + .try_push(UnlockChunk { value, era }) .map_err(|_| Error::::NoMoreChunks)?; }; // NOTE: ledger must be updated prior to calling `Self::weight_of`. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 9cdeed4a2fc41..2de9949a4ec8c 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -1257,7 +1257,7 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 1000, unlock_era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], claimed_rewards: vec![] }), ); @@ -1270,7 +1270,7 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 1000, unlock_era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], claimed_rewards: vec![] }), ); @@ -1286,7 +1286,7 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 1000, unlock_era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], claimed_rewards: vec![] }), ); @@ -1391,7 +1391,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 900, unlock_era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 900, era: 2 + 3 }], claimed_rewards: vec![], }) ); @@ -1417,7 +1417,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 900, unlock_era: 5 }], + unlocking: bounded_vec![UnlockChunk { value: 900, era: 5 }], claimed_rewards: vec![], }) ); @@ -1430,7 +1430,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 600, - unlocking: bounded_vec![UnlockChunk { value: 400, unlock_era: 5 }], + unlocking: bounded_vec![UnlockChunk { value: 400, era: 5 }], claimed_rewards: vec![], }) ); @@ -1458,7 +1458,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 900, unlock_era: 5 }], + unlocking: bounded_vec![UnlockChunk { value: 900, era: 5 }], claimed_rewards: vec![], }) ); @@ -1471,7 +1471,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 600, - unlocking: bounded_vec![UnlockChunk { value: 400, unlock_era: 5 }], + unlocking: bounded_vec![UnlockChunk { value: 400, era: 5 }], claimed_rewards: vec![], }) ); @@ -1513,7 +1513,7 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 600, - unlocking: bounded_vec![UnlockChunk { value: 400, unlock_era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 400, era: 2 + 3 }], claimed_rewards: vec![], }) ); @@ -1529,8 +1529,8 @@ fn rebond_is_fifo() { total: 1000, active: 300, unlocking: bounded_vec![ - UnlockChunk { value: 400, unlock_era: 2 + 3 }, - UnlockChunk { value: 300, unlock_era: 3 + 3 }, + UnlockChunk { value: 400, era: 2 + 3 }, + UnlockChunk { value: 300, era: 3 + 3 }, ], claimed_rewards: vec![], }) @@ -1547,9 +1547,9 @@ fn rebond_is_fifo() { total: 1000, active: 100, unlocking: bounded_vec![ - UnlockChunk { value: 400, unlock_era: 2 + 3 }, - UnlockChunk { value: 300, unlock_era: 3 + 3 }, - UnlockChunk { value: 200, unlock_era: 4 + 3 }, + UnlockChunk { value: 400, era: 2 + 3 }, + UnlockChunk { value: 300, era: 3 + 3 }, + UnlockChunk { value: 200, era: 4 + 3 }, ], claimed_rewards: vec![], }) @@ -1564,8 +1564,8 @@ fn rebond_is_fifo() { total: 1000, active: 500, unlocking: bounded_vec![ - UnlockChunk { value: 400, unlock_era: 2 + 3 }, - UnlockChunk { value: 100, unlock_era: 3 + 3 }, + UnlockChunk { value: 400, era: 2 + 3 }, + UnlockChunk { value: 100, era: 3 + 3 }, ], claimed_rewards: vec![], }) @@ -1595,7 +1595,7 @@ fn rebond_emits_right_value_in_event() { stash: 11, total: 1000, active: 100, - unlocking: bounded_vec![UnlockChunk { value: 900, unlock_era: 1 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 900, era: 1 + 3 }], claimed_rewards: vec![], }) ); @@ -1608,7 +1608,7 @@ fn rebond_emits_right_value_in_event() { stash: 11, total: 1000, active: 200, - unlocking: bounded_vec![UnlockChunk { value: 800, unlock_era: 1 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 800, era: 1 + 3 }], claimed_rewards: vec![], }) ); @@ -1839,7 +1839,7 @@ fn bond_with_no_staked_value() { stash: 1, active: 0, total: 5, - unlocking: bounded_vec![UnlockChunk { value: 5, unlock_era: 3 }], + unlocking: bounded_vec![UnlockChunk { value: 5, era: 3 }], claimed_rewards: vec![], }) ); @@ -3919,7 +3919,7 @@ fn cannot_rebond_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 0, - unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, unlock_era: 3 }], + unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, era: 3 }], claimed_rewards: vec![] } ); @@ -3956,7 +3956,7 @@ fn cannot_bond_extra_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 0, - unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, unlock_era: 3 }], + unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, era: 3 }], claimed_rewards: vec![] } ); @@ -4809,7 +4809,7 @@ fn force_apply_min_commission_works() { #[test] fn ledger_slash_works() { - let c = |unlock_era, value| UnlockChunk:: { unlock_era, value }; + let c = |era, value| UnlockChunk:: { era, value }; // Given let mut ledger = StakingLedger:: { stash: 123, From dc4340147fa89b04a3e2ad64654c0c079f3dbb33 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 8 Apr 2022 15:09:58 -0400 Subject: [PATCH 248/299] Fix accidental frame-support regression --- frame/support/src/lib.rs | 2 +- primitives/staking/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 8f4d7d0d9cb23..714449eec7847 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -200,7 +200,7 @@ macro_rules! generate_storage_alias { => DoubleMap<($hasher1:ty, $key1:ty), ($hasher2:ty, $key2:ty), $value:ty $(, $querytype:ty)?> ) => { $crate::paste::paste! { - $crate::generate_storage_alias!(@ $pallet, $name); + $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); type $name = $crate::storage::types::StorageDoubleMap< [<$name Instance>], $hasher1, diff --git a/primitives/staking/Cargo.toml b/primitives/staking/Cargo.toml index 5847e8225c1a7..ac6c9d0e6428c 100644 --- a/primitives/staking/Cargo.toml +++ b/primitives/staking/Cargo.toml @@ -26,4 +26,4 @@ std = [ "sp-runtime/std", "sp-std/std", ] -runtime-benchmarks = [] \ No newline at end of file +runtime-benchmarks = [] From 24c185017c2f77458e3dde195bbfc7adcbef0cdb Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 8 Apr 2022 15:22:23 -0400 Subject: [PATCH 249/299] Fix issue with transactions in tests --- frame/nomination-pools/src/tests.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 949a9e88e9abe..9dc35761fd7d7 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -17,7 +17,10 @@ use super::*; use crate::mock::*; -use frame_support::{assert_noop, assert_ok, assert_storage_noop}; +use frame_support::{ + assert_noop, assert_ok, assert_storage_noop, + storage::{with_transaction, TransactionOutcome}, +}; macro_rules! sub_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ @@ -564,8 +567,6 @@ mod join { } mod claim_payout { - use frame_support::storage::with_transaction; - use super::*; fn del(points: Balance, reward_pool_total_earnings: Balance) -> Delegator { @@ -761,7 +762,7 @@ mod claim_payout { #[test] fn do_reward_payout_correctly_sets_pool_state_to_destroying() { ExtBuilder::default().build_and_execute(|| { - with_transaction(|| { + with_transaction(|| -> TransactionOutcome { let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); @@ -782,11 +783,11 @@ mod claim_payout { // Then assert!(bonded_pool.is_destroying()); - storage::TransactionOutcome::Rollback(()) + storage::TransactionOutcome::Rollback(Ok(())) }); // -- current_points saturates (reward_pool.points + new_earnings * bonded_pool.points) - with_transaction(|| { + with_transaction(|| -> TransactionOutcome { // Given let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); @@ -809,8 +810,8 @@ mod claim_payout { // Then assert!(bonded_pool.is_destroying()); - storage::TransactionOutcome::Rollback(()) - }) + storage::TransactionOutcome::Rollback(Ok(())) + }); }); } From 5e2938d51756789c02cb3a7185185008b2a76ca1 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 8 Apr 2022 17:49:19 -0400 Subject: [PATCH 250/299] Fix doc links --- frame/nomination-pools/src/lib.rs | 33 ++++++++++++++++--------------- primitives/staking/src/lib.rs | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 2eac738721280..bcabda6586021 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -214,7 +214,7 @@ //! rewards for the active era despite not contributing to the pool's vote weight. If it joins //! after the election snapshot is taken it will benefit from the rewards of the next _2_ eras //! because it's vote weight will not be counted until the election snapshot in active era + 1. -//! Related: https://github.com/paritytech/substrate/issues/10861 +//! Related: //! //! **Relevant extrinsics:** //! @@ -268,7 +268,7 @@ //! ### Slashing //! //! This section assumes that the slash computation is executed by -//! [`pallet_staking::StakingLedger::slash`], which passes the information to this pallet via +//! `pallet_staking::StakingLedger::slash`, which passes the information to this pallet via //! [`sp_staking::OnStakerSlash::on_slash`]. //! //! Unbonding pools need to be slashed to ensure all nominators whom where in the bonded pool @@ -282,11 +282,11 @@ //! //! To be fair to joiners, this implementation also need joining pools, which are actively staking, //! in addition to the unbonding pools. For maintenance simplicity these are not implemented. -//! Related: https://github.com/paritytech/substrate/issues/10860 +//! Related: //! //! **Relevant methods:** //! -//! * [`Pallet::do_slash`] +//! * [`Pallet::on_slash`] //! //! ### Limitations //! @@ -340,7 +340,7 @@ pub type PoolId = u32; type SubPoolsWithEra = BoundedBTreeMap, TotalUnbondingPools>; -const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; +pub const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; /// Possible operations on the configuration values of this pallet. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq, Clone)] @@ -431,11 +431,11 @@ pub struct PoolRoles { #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct BondedPoolInner { - /// See [`BondedPool::points`]. + /// Total points of all the delegators in the pool who are actively bonded. pub points: BalanceOf, - /// See [`BondedPool::state_toggler`]. + /// The current state of the pool. pub state: PoolState, - /// See [`BondedPool::delegator_counter`] + /// Count of delegators that belong to the pool. pub delegator_counter: u32, /// See [`PoolRoles`]. pub roles: PoolRoles, @@ -958,7 +958,8 @@ pub mod pallet { #[pallet::storage] pub type Delegators = CountedStorageMap<_, Twox64Concat, T::AccountId, Delegator>; - /// To get or insert a pool see [`BondedPool::get`] and [`BondedPool::put`] + /// Storage for bonded pools. + // To get or insert a pool see [`BondedPool::get`] and [`BondedPool::put`] #[pallet::storage] pub type BondedPools = CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner>; @@ -1090,7 +1091,7 @@ pub mod pallet { CanNotChangeState, /// The caller does not have adequate permissions. DoesNotHavePermission, - /// Metadata exceeds [`T::MaxMetadataLen`] + /// Metadata exceeds [`Config::MaxMetadataLen`] MetadataExceedsMaxLen, /// Some error occurred that should never happen. This should be reported to the /// maintainers. @@ -1242,7 +1243,7 @@ pub mod pallet { /// # Note /// /// If there are too many unlocking chunks to unbond with the pool account, - /// [`Self::withdraw_unbonded_pool`] can be called to try and minimize unlocking chunks. If + /// [`Call::pool_withdraw_unbonded`] can be called to try and minimize unlocking chunks. If /// there are too many unlocking chunks, the result of this call will likely be the /// `NoMoreChunks` error from the staking system. #[pallet::weight(T::WeightInfo::unbond_other())] @@ -1616,11 +1617,11 @@ pub mod pallet { /// /// # Arguments /// - /// * `min_join_bond` - Set [`Self::MinJoinBond`]. - /// * `min_create_bond` - Set [`Self::MinCreateBond`]. - /// * `max_pools` - Set [`Self::MaxPools`]. - /// * `max_delegators` - Set [`Self::MaxDelegators`]. - /// * `max_delegators_per_pool` - Set [`Self::MaxDelegatorsPerPool`]. + /// * `min_join_bond` - Set [`MinJoinBond`]. + /// * `min_create_bond` - Set [`MinCreateBond`]. + /// * `max_pools` - Set [`MaxPools`]. + /// * `max_delegators` - Set [`MaxDelegators`]. + /// * `max_delegators_per_pool` - Set [`MaxDelegatorsPerPool`]. #[pallet::weight(T::WeightInfo::set_configs())] pub fn set_configs( origin: OriginFor, diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 24cf794ee8436..4f24d210a05e9 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -83,7 +83,7 @@ pub trait StakingInterface { fn active_stake(controller: &Self::AccountId) -> Option; /// The total stake that `controller` has in the staking system. This includes the - /// [`active_stake`], and any funds currently in the process of unbonding via [`unbond`]. + /// [`Self::active_stake`], and any funds currently in the process of unbonding via [`Self::unbond`]. /// /// # Note /// From 8a1dc50c275da7372217c3a856be26a3db71f9fc Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Sat, 9 Apr 2022 22:19:20 -0400 Subject: [PATCH 251/299] Make sure result in test is used --- frame/nomination-pools/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 9dc35761fd7d7..d1cb7f22c2349 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -762,7 +762,7 @@ mod claim_payout { #[test] fn do_reward_payout_correctly_sets_pool_state_to_destroying() { ExtBuilder::default().build_and_execute(|| { - with_transaction(|| -> TransactionOutcome { + let _ = with_transaction(|| -> TransactionOutcome { let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); let mut delegator = Delegators::::get(10).unwrap(); @@ -787,7 +787,7 @@ mod claim_payout { }); // -- current_points saturates (reward_pool.points + new_earnings * bonded_pool.points) - with_transaction(|| -> TransactionOutcome { + let _ = with_transaction(|| -> TransactionOutcome { // Given let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); From bcbea4e693d9a602ee8dd6b7e797f12cda2d4ac9 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Mon, 11 Apr 2022 10:17:08 -0400 Subject: [PATCH 252/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index bcabda6586021..b18eb36b13f41 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1718,7 +1718,7 @@ impl Pallet { new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (true, true) | (false, true) => + (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { // The pool was totally slashed. From 91178a68ee4dd81bff0d8dc3006bd3f8f1ba0616 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 11 Apr 2022 10:19:55 -0400 Subject: [PATCH 253/299] Fix can toggle state --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index bcabda6586021..45f010cf4580c 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -567,7 +567,7 @@ impl BondedPool { } fn can_toggle_state(&self, who: &T::AccountId) -> bool { - *who == self.roles.root || *who == self.roles.state_toggler && !self.is_destroying() + (*who == self.roles.root || *who == self.roles.state_toggler) && !self.is_destroying() } fn can_set_metadata(&self, who: &T::AccountId) -> bool { From a0052b71dc34b7ab71b4c976b42166600e7f91a2 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 11 Apr 2022 10:41:41 -0400 Subject: [PATCH 254/299] Account for new_funds in ok to be open --- frame/nomination-pools/src/lib.rs | 19 ++++++++++--------- frame/nomination-pools/src/tests.rs | 12 ++++++------ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 167c85065ef7b..69ce144f67758 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -580,7 +580,7 @@ impl BondedPool { /// Whether or not the pool is ok to be in `PoolSate::Open`. If this returns an `Err`, then the /// pool is unrecoverable and should be in the destroying state. - fn ok_to_be_open(&self) -> Result<(), DispatchError> { + fn ok_to_be_open(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { ensure!(!self.is_destroying(), Error::::CanNotChangeState); let bonded_balance = @@ -588,17 +588,18 @@ impl BondedPool { ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); let points_to_balance_ratio_floor = self - .points - // We checked for zero above - .div(bonded_balance); + .points + // We checked for zero above + .div(bonded_balance); // Pool points can inflate relative to balance, but only if the pool is slashed. // If we cap the ratio of points:balance so one cannot join a pool that has been slashed // 90%, ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); // while restricting the balance to 1/10th of max total issuance, + let next_bonded_balance = bonded_balance.saturating_add(new_funds); ensure!( - bonded_balance < BalanceOf::::max_value().div(10u32.into()), + next_bonded_balance < BalanceOf::::max_value().div(10u32.into()), Error::::OverflowRisk ); // then we can be decently confident the bonding pool points will not overflow @@ -608,9 +609,9 @@ impl BondedPool { } /// Check that the pool can accept a member with `new_funds`. - fn ok_to_join(&self) -> Result<(), DispatchError> { + fn ok_to_join(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { ensure!(self.state == PoolState::Open, Error::::NotOpen); - self.ok_to_be_open()?; + self.ok_to_be_open(new_funds)?; Ok(()) } @@ -1121,7 +1122,7 @@ pub mod pallet { ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; - bonded_pool.ok_to_join()?; + bonded_pool.ok_to_join(amount)?; // We just need its total earnings at this point in time, but we don't need to write it // because we are not adjusting its points (all other values can calculated virtual). @@ -1579,7 +1580,7 @@ pub mod pallet { if bonded_pool.can_toggle_state(&who) { bonded_pool.set_state(state); - } else if bonded_pool.ok_to_be_open().is_err() && state == PoolState::Destroying { + } else if bonded_pool.ok_to_be_open(Zero::zero()).is_err() && state == PoolState::Destroying { // If the pool has bad properties, then anyone can set it as destroying bonded_pool.set_state(PoolState::Destroying); } else { diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index d1cb7f22c2349..ed3b954ee4a14 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -186,22 +186,22 @@ mod bonded_pool { // Simulate a 100% slashed pool StakingMock::set_bonded_balance(pool.bonded_account(), 0); - assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); + assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); // Simulate a 89% StakingMock::set_bonded_balance(pool.bonded_account(), 11); - assert_ok!(pool.ok_to_join()); + assert_ok!(pool.ok_to_join(0)); // Simulate a 90% slashed pool StakingMock::set_bonded_balance(pool.bonded_account(), 10); - assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); + assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); StakingMock::set_bonded_balance(pool.bonded_account(), Balance::MAX / 10); // New bonded balance would be over 1/10th of Balance type - assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); + assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); // and a sanity check StakingMock::set_bonded_balance(pool.bonded_account(), Balance::MAX / 10 - 1); - assert_ok!(pool.ok_to_join()); + assert_ok!(pool.ok_to_join(0)); }); } } @@ -2339,7 +2339,7 @@ mod set_state_other { fn set_state_other_works() { ExtBuilder::default().build_and_execute(|| { // Given - assert_ok!(BondedPool::::get(1).unwrap().ok_to_be_open()); + assert_ok!(BondedPool::::get(1).unwrap().ok_to_be_open(0)); // Only the root and state toggler can change the state when the pool is ok to be open. assert_noop!( From 4c71c4b1e17bd7ebfb2bf6b2cc2b08b69a058c05 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:31:16 -0400 Subject: [PATCH 255/299] Update docs: ok_to_withdraw_unbonded_other_with --- frame/nomination-pools/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 69ce144f67758..5e12facd4e19f 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -646,8 +646,11 @@ impl BondedPool { Ok(()) } - /// Returns Ok if [`Pallet::withdraw_unbonded_other`] can be executed, otherwise returns a - /// dispatch error. + /// # Returns + /// + /// * Ok(true) if [`Call::withdraw_unbonded_other`] can be called and the target account is the depositor. + /// * Ok(false) if [`Call::withdraw_unbonded_other`] can be called and target account is *not* the depositor. + /// * Err(DispatchError) if [`Call::withdraw_unbonded_other`] *cannot* be called. fn ok_to_withdraw_unbonded_other_with( &self, caller: &T::AccountId, From 7af61f8d19086218ae2e016ac4a5b296af7aae45 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Mon, 11 Apr 2022 11:31:46 -0400 Subject: [PATCH 256/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi --- frame/nomination-pools/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 5e12facd4e19f..ccfb9e4fab7ba 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1112,6 +1112,7 @@ pub mod pallet { /// # Note /// /// * An account can only be a member of a single pool. + /// * An account cannot join the same pool multiple times. /// * This call will *not* dust the delegator account, so the delegator must have at least /// `existential deposit + amount` in their account. /// * Only a pool with [`PoolState::Open`] can be joined From 4baa7c8a411cd1a87c6cdecd77a18cfe5d1813d3 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Mon, 11 Apr 2022 11:35:01 -0400 Subject: [PATCH 257/299] Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi --- frame/nomination-pools/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index ccfb9e4fab7ba..2161754ad7754 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1761,8 +1761,7 @@ impl Pallet { /// Calculate the rewards for `delegator`. /// - /// Returns the payout amount, and whether the pool state has been switched to destroying during - /// this call. + /// Returns the payout amount. fn calculate_delegator_payout( bonded_pool: &mut BondedPool, reward_pool: &mut RewardPool, From 630370d8f397c469f797aaa8a4e676d793a907e4 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:35:34 -0400 Subject: [PATCH 258/299] Remove some staking comments --- frame/nomination-pools/src/lib.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index ccfb9e4fab7ba..1402dc1f1e65f 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1768,17 +1768,6 @@ impl Pallet { reward_pool: &mut RewardPool, delegator: &mut Delegator, ) -> Result, DispatchError> { - // Presentation Notes: - // Reward pool points - // Essentially we make it so each plank is inflated by the number of points in bonded pool. - // So if we have earned 10 plank and 100 bonded pool points, we get 1,000 reward pool - // points. The delegator scales up their points as well (say 10 for this example) and we get - // the delegator has virtual points of 10points * 10rewards (100reward-points). - // So the payout calc is 100 / 1,000 * 100 = 10 - // - // Keep in mind we subtract the delegators virtual points from the pool points to account - // for the fact that we transferred their portion of rewards out of the pool account. - let u256 = |x| T::BalanceToU256::convert(x); let balance = |x| T::U256ToBalance::convert(x); // If the delegator is unbonding they cannot claim rewards. Note that when the delegator From 0985fa2944f75cd999f6d6b93b5f1fa6bd2c8a43 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:45:03 -0400 Subject: [PATCH 259/299] Rename SubPoolsWithEra to UnbondingPoolsWithEra --- Cargo.lock | 2 +- frame/nomination-pools/src/lib.rs | 6 ++--- frame/nomination-pools/src/tests.rs | 38 ++++++++++++++--------------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5e28f12cfedc..e7b5298b204a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9149,7 +9149,7 @@ dependencies = [ name = "sc-sysinfo" version = "6.0.0-dev" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "libc", "log 0.4.14", "rand 0.7.3", diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 12b05b634246e..9be53a8a27f6f 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -338,7 +338,7 @@ pub type RewardPoints = U256; /// Type used for unique identifier of each pool. pub type PoolId = u32; -type SubPoolsWithEra = BoundedBTreeMap, TotalUnbondingPools>; +type UnbondingPoolsWithEra = BoundedBTreeMap, TotalUnbondingPools>; pub const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; @@ -843,7 +843,7 @@ pub struct SubPools { /// older then `current_era - TotalUnbondingPools`. no_era: UnbondPool, /// Map of era in which a pool becomes unbonded in => unbond pools. - with_era: SubPoolsWithEra, + with_era: UnbondingPoolsWithEra, } impl SubPools { @@ -881,7 +881,7 @@ pub struct TotalUnbondingPools(PhantomData); impl Get for TotalUnbondingPools { fn get() -> u32 { // NOTE: this may be dangerous in the scenario bonding_duration gets decreased because - // we would no longer be able to decode `SubPoolsWithEra`, which uses `TotalUnbondingPools` + // we would no longer be able to decode `UnbondingPoolsWithEra`, which uses `TotalUnbondingPools` // as the bound T::StakingInterface::bonding_duration() + T::PostUnbondingPoolsWindow::get() } diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index ed3b954ee4a14..61b8fa1e2d535 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -22,11 +22,11 @@ use frame_support::{ storage::{with_transaction, TransactionOutcome}, }; -macro_rules! sub_pools_with_era { +macro_rules! unbonding_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ use sp_std::iter::{Iterator, IntoIterator}; let not_bounded: BTreeMap<_, _> = Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*])); - SubPoolsWithEra::try_from(not_bounded).unwrap() + UnbondingPoolsWithEra::try_from(not_bounded).unwrap() }}; } @@ -322,7 +322,7 @@ mod sub_pools { // Given let mut sub_pool_0 = SubPools:: { no_era: UnbondPool::::default(), - with_era: sub_pools_with_era! { + with_era: unbonding_pools_with_era! { 0 => UnbondPool:: { points: 10, balance: 10 }, 1 => UnbondPool:: { points: 10, balance: 10 }, 2 => UnbondPool:: { points: 20, balance: 20 }, @@ -1381,7 +1381,7 @@ mod unbond { assert_eq!( SubPoolsStorage::::get(1).unwrap().with_era, - sub_pools_with_era! { 0 + 3 => UnbondPool:: { points: 10, balance: 10 }} + unbonding_pools_with_era! { 0 + 3 => UnbondPool:: { points: 10, balance: 10 }} ); assert_eq!( @@ -1418,7 +1418,7 @@ mod unbond { // Then assert_eq!( SubPoolsStorage::::get(1).unwrap().with_era, - sub_pools_with_era! { 0 + 3 => UnbondPool { points: 6, balance: 6 }} + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 6, balance: 6 }} ); assert_eq!( BondedPool::::get(1).unwrap(), @@ -1444,7 +1444,7 @@ mod unbond { // Then assert_eq!( SubPoolsStorage::::get(&1).unwrap().with_era, - sub_pools_with_era! { 0 + 3 => UnbondPool { points: 98, balance: 98 }} + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 98, balance: 98 }} ); assert_eq!( BondedPool::::get(1).unwrap(), @@ -1468,7 +1468,7 @@ mod unbond { // Then assert_eq!( SubPoolsStorage::::get(1).unwrap().with_era, - sub_pools_with_era! { 0 + 3 => UnbondPool { points: 100, balance: 100 }} + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 100, balance: 100 }} ); assert_eq!( BondedPool::::get(1).unwrap(), @@ -1497,7 +1497,7 @@ mod unbond { 1, SubPools { no_era: Default::default(), - with_era: sub_pools_with_era! { + with_era: unbonding_pools_with_era! { 0 + 3 => UnbondPool { balance: 10, points: 100 }, 1 + 3 => UnbondPool { balance: 20, points: 20 }, 2 + 3 => UnbondPool { balance: 101, points: 101} @@ -1517,7 +1517,7 @@ mod unbond { SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: UnbondPool { balance: 10 + 20, points: 100 + 20 }, - with_era: sub_pools_with_era! { + with_era: unbonding_pools_with_era! { 2 + 3 => UnbondPool { balance: 101, points: 101}, current_era + 3 => UnbondPool { balance: 10, points: 10 }, }, @@ -1568,7 +1568,7 @@ mod unbond { SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: Default::default(), - with_era: sub_pools_with_era! { + with_era: unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 100 + 200, balance: 100 + 200 } }, } @@ -1626,7 +1626,7 @@ mod unbond { SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: Default::default(), - with_era: sub_pools_with_era! { + with_era: unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 110, balance: 110 } } } @@ -1824,7 +1824,7 @@ mod withdraw_unbonded_other { 1, SubPools { no_era: Default::default(), - with_era: sub_pools_with_era! { 0 + 3 => UnbondPool { points: 600, balance: 100 }}, + with_era: unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 600, balance: 100 }}, }, ); CurrentEra::set(StakingMock::bonding_duration()); @@ -1835,7 +1835,7 @@ mod withdraw_unbonded_other { // Then assert_eq!( SubPoolsStorage::::get(&1).unwrap().with_era, - sub_pools_with_era! { 0 + 3 => UnbondPool { points: 560, balance: 94 }} + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 560, balance: 94 }} ); assert_eq!(Balances::free_balance(&40), 40 + 6); assert_eq!(Balances::free_balance(&default_bonded_account()), 94); @@ -1847,7 +1847,7 @@ mod withdraw_unbonded_other { // Then assert_eq!( SubPoolsStorage::::get(&1).unwrap().with_era, - sub_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 2 }} + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 2 }} ); assert_eq!(Balances::free_balance(&550), 550 + 92); // The account was dusted because it went below ED(5) @@ -1883,7 +1883,7 @@ mod withdraw_unbonded_other { assert_eq!( SubPoolsStorage::::get(1).unwrap().with_era, //------------------------------balance decrease is not account for - sub_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 } } + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 } } ); CurrentEra::set(0 + 3); @@ -1903,7 +1903,7 @@ mod withdraw_unbonded_other { // Insert the sub-pool let sub_pools = SubPools { no_era: Default::default(), - with_era: sub_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 }}, + with_era: unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 }}, }; SubPoolsStorage::::insert(123, sub_pools.clone()); @@ -2051,7 +2051,7 @@ mod withdraw_unbonded_other { SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: Default::default(), - with_era: sub_pools_with_era! { + with_era: unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 100, balance: 100}, 1 + 3 => UnbondPool { points: 200 + 10, balance: 200 + 10 } } @@ -2073,7 +2073,7 @@ mod withdraw_unbonded_other { SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: Default::default(), - with_era: sub_pools_with_era! { + with_era: unbonding_pools_with_era! { // Note that era 0+3 unbond pool is destroyed because points went to 0 1 + 3 => UnbondPool { points: 200 + 10, balance: 200 + 10 } } @@ -2092,7 +2092,7 @@ mod withdraw_unbonded_other { SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: Default::default(), - with_era: sub_pools_with_era! { + with_era: unbonding_pools_with_era! { 1 + 3 => UnbondPool { points: 10, balance: 10 } } } From 9190415a450c7f8b78b763d235bc7cc55608ee85 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:57:53 -0400 Subject: [PATCH 260/299] Use validators length for benchmarks --- frame/nomination-pools/benchmarking/src/lib.rs | 6 ++++-- frame/nomination-pools/benchmarking/src/mock.rs | 2 +- frame/nomination-pools/src/lib.rs | 2 +- frame/nomination-pools/src/weights.rs | 6 +++--- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 1648f1b050aa5..b66df94876288 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -123,7 +123,7 @@ impl ListScenario { // Find a destination weight that will trigger the worst case scenario let dest_weight_as_vote = - ::SortedListProvider::score_update_worst_case( + ::VoterList::score_update_worst_case( &pool_origin1, is_increase, ); @@ -514,6 +514,8 @@ frame_benchmarking::benchmarks! { } nominate { + let n in 1 .. T::MaxNominations::get(); + // Create a pool let min_create_bond = MinCreateBond::::get() .max(T::StakingInterface::minimum_bond()) @@ -522,7 +524,7 @@ frame_benchmarking::benchmarks! { // Create some accounts to nominate. For the sake of benchmarking they don't need to be // actual validators - let validators: Vec<_> = (0..T::MaxNominations::get()) + let validators: Vec<_> = (0..n) .map(|i| account("stash", USER_SEED, i)) .collect(); diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 19776f8be4675..3bc4f72d472f9 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -107,7 +107,7 @@ impl pallet_staking::Config for Runtime { type ElectionProvider = frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; type GenesisElectionProvider = Self::ElectionProvider; - type SortedListProvider = pallet_bags_list::Pallet; + type VoterList = pallet_bags_list::Pallet; type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 9be53a8a27f6f..e8944bfce4b9a 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1559,7 +1559,7 @@ pub mod pallet { Ok(()) } - #[pallet::weight(T::WeightInfo::nominate())] + #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))] pub fn nominate( origin: OriginFor, pool_id: PoolId, diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index fc60e583ea9d4..58b4c3487ca68 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -52,7 +52,7 @@ pub trait WeightInfo { fn withdraw_unbonded_other_update(s: u32, ) -> Weight; fn withdraw_unbonded_other_kill(s: u32, ) -> Weight; fn create() -> Weight; - fn nominate() -> Weight; + fn nominate(n: u32) -> Weight; fn set_state_other() -> Weight; fn set_metadata() -> Weight; fn set_configs() -> Weight; @@ -182,7 +182,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) - fn nominate() -> Weight { + fn nominate(n: u32) -> Weight { (79_587_000 as Weight) .saturating_add(T::DbWeight::get().reads(28 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) @@ -328,7 +328,7 @@ impl WeightInfo for () { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) - fn nominate() -> Weight { + fn nominate(_n: u32) -> Weight { (79_587_000 as Weight) .saturating_add(RocksDbWeight::get().reads(28 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) From d31afbfe181177196599c43429599f99371556af Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 11 Apr 2022 12:06:00 -0400 Subject: [PATCH 261/299] Use metadata length for benchmarks --- .../nomination-pools/benchmarking/src/lib.rs | 13 ++++----- frame/nomination-pools/src/lib.rs | 27 ++++++++++--------- frame/nomination-pools/src/weights.rs | 8 +++--- primitives/staking/src/lib.rs | 3 ++- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index b66df94876288..8c3948c5a475b 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -122,11 +122,10 @@ impl ListScenario { )?; // Find a destination weight that will trigger the worst case scenario - let dest_weight_as_vote = - ::VoterList::score_update_worst_case( - &pool_origin1, - is_increase, - ); + let dest_weight_as_vote = ::VoterList::score_update_worst_case( + &pool_origin1, + is_increase, + ); let dest_weight: BalanceOf = dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; @@ -573,6 +572,8 @@ frame_benchmarking::benchmarks! { } set_metadata { + let n in 1 .. ::MaxMetadataLen::get(); + // Create a pool let min_create_bond = MinCreateBond::::get() .max(T::StakingInterface::minimum_bond()) @@ -580,7 +581,7 @@ frame_benchmarking::benchmarks! { let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Create metadata of the max possible size - let metadata: Vec = (0..::MaxMetadataLen::get()).map(|_| 42).collect(); + let metadata: Vec = (0..n).map(|_| 42).collect(); whitelist_account!(depositor); }:_(Origin::Signed(depositor), 1, metadata.clone()) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index e8944bfce4b9a..ebd19a3078309 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -588,9 +588,9 @@ impl BondedPool { ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); let points_to_balance_ratio_floor = self - .points - // We checked for zero above - .div(bonded_balance); + .points + // We checked for zero above + .div(bonded_balance); // Pool points can inflate relative to balance, but only if the pool is slashed. // If we cap the ratio of points:balance so one cannot join a pool that has been slashed @@ -648,8 +648,10 @@ impl BondedPool { /// # Returns /// - /// * Ok(true) if [`Call::withdraw_unbonded_other`] can be called and the target account is the depositor. - /// * Ok(false) if [`Call::withdraw_unbonded_other`] can be called and target account is *not* the depositor. + /// * Ok(true) if [`Call::withdraw_unbonded_other`] can be called and the target account is the + /// depositor. + /// * Ok(false) if [`Call::withdraw_unbonded_other`] can be called and target account is *not* + /// the depositor. /// * Err(DispatchError) if [`Call::withdraw_unbonded_other`] *cannot* be called. fn ok_to_withdraw_unbonded_other_with( &self, @@ -881,8 +883,8 @@ pub struct TotalUnbondingPools(PhantomData); impl Get for TotalUnbondingPools { fn get() -> u32 { // NOTE: this may be dangerous in the scenario bonding_duration gets decreased because - // we would no longer be able to decode `UnbondingPoolsWithEra`, which uses `TotalUnbondingPools` - // as the bound + // we would no longer be able to decode `UnbondingPoolsWithEra`, which uses + // `TotalUnbondingPools` as the bound T::StakingInterface::bonding_duration() + T::PostUnbondingPoolsWindow::get() } } @@ -1112,7 +1114,7 @@ pub mod pallet { /// # Note /// /// * An account can only be a member of a single pool. - /// * An account cannot join the same pool multiple times. + /// * An account cannot join the same pool multiple times. /// * This call will *not* dust the delegator account, so the delegator must have at least /// `existential deposit + amount` in their account. /// * Only a pool with [`PoolState::Open`] can be joined @@ -1584,7 +1586,9 @@ pub mod pallet { if bonded_pool.can_toggle_state(&who) { bonded_pool.set_state(state); - } else if bonded_pool.ok_to_be_open(Zero::zero()).is_err() && state == PoolState::Destroying { + } else if bonded_pool.ok_to_be_open(Zero::zero()).is_err() && + state == PoolState::Destroying + { // If the pool has bad properties, then anyone can set it as destroying bonded_pool.set_state(PoolState::Destroying); } else { @@ -1596,7 +1600,7 @@ pub mod pallet { Ok(()) } - #[pallet::weight(T::WeightInfo::set_metadata())] + #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))] pub fn set_metadata( origin: OriginFor, pool_id: PoolId, @@ -1723,8 +1727,7 @@ impl Pallet { new_funds: BalanceOf, ) -> BalanceOf { match (current_balance.is_zero(), current_points.is_zero()) { - (_, true) => - new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), + (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { // The pool was totally slashed. // This is the equivalent of `(current_points / 1) * new_funds`. diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index 58b4c3487ca68..f29b40c8e1fc0 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -54,7 +54,7 @@ pub trait WeightInfo { fn create() -> Weight; fn nominate(n: u32) -> Weight; fn set_state_other() -> Weight; - fn set_metadata() -> Weight; + fn set_metadata(_n: u32) -> Weight; fn set_configs() -> Weight; } @@ -182,7 +182,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) - fn nominate(n: u32) -> Weight { + fn nominate(_n: u32) -> Weight { (79_587_000 as Weight) .saturating_add(T::DbWeight::get().reads(28 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) @@ -192,7 +192,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(28 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } - fn set_metadata() -> Weight { + fn set_metadata(_n: u32) -> Weight { (79_587_000 as Weight) .saturating_add(T::DbWeight::get().reads(28 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) @@ -338,7 +338,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(28 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } - fn set_metadata() -> Weight { + fn set_metadata(_n: u32) -> Weight { (79_587_000 as Weight) .saturating_add(RocksDbWeight::get().reads(28 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 4f24d210a05e9..7b118d15cfbdd 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -83,7 +83,8 @@ pub trait StakingInterface { fn active_stake(controller: &Self::AccountId) -> Option; /// The total stake that `controller` has in the staking system. This includes the - /// [`Self::active_stake`], and any funds currently in the process of unbonding via [`Self::unbond`]. + /// [`Self::active_stake`], and any funds currently in the process of unbonding via + /// [`Self::unbond`]. /// /// # Note /// From fd907c62aaa1333fa394436145f106903566cdb9 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 11 Apr 2022 15:03:33 -0400 Subject: [PATCH 262/299] Remove debug assert eq --- frame/nomination-pools/src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index ebd19a3078309..74eece9ed68c9 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1447,10 +1447,6 @@ pub mod pallet { // Kill accounts from storage by making their balance go below ED. We assume that // the accounts have no references that would prevent destruction once we get to // this point. - debug_assert_eq!( - T::Currency::free_balance(&bonded_pool.bonded_account()), - Zero::zero() - ); debug_assert_eq!( T::StakingInterface::total_stake(&bonded_pool.bonded_account()) .unwrap_or_default(), From 8c2e5e1f7af65ec925a3126a59d1f0c053806547 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 11 Apr 2022 16:49:56 -0400 Subject: [PATCH 263/299] docs --- frame/nomination-pools/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 74eece9ed68c9..e86aa137cfc44 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1348,13 +1348,13 @@ pub mod pallet { /// Withdraw unbonded funds for the `target` delegator. Under certain conditions, /// this call can be dispatched permissionlessly (i.e. by any account). /// - /// # Conditions for a permissionless dispatch + /// # Conditions for a permissionless dispatch /// /// * The pool is in destroy mode and the target is not the depositor. /// * The target is the depositor and they are the only delegator in the sub pools. /// * The pool is blocked and the caller is either the root or state-toggler. /// - /// # Conditions for permissioned dispatch + /// # Conditions for permissioned dispatch /// /// * The caller is the target and they are not the depositor. /// From fea5f78d502e89d3d5361db7e583ad68a7287a51 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:46:25 -0700 Subject: [PATCH 264/299] Fix test: withdraw_unbonded_other_errors_correctly --- frame/nomination-pools/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 61b8fa1e2d535..e1b96eb4a06d9 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1905,7 +1905,7 @@ mod withdraw_unbonded_other { no_era: Default::default(), with_era: unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 }}, }; - SubPoolsStorage::::insert(123, sub_pools.clone()); + SubPoolsStorage::::insert(1, sub_pools.clone()); assert_noop!( Pools::withdraw_unbonded_other(Origin::signed(11), 11, 0), @@ -1939,7 +1939,7 @@ mod withdraw_unbonded_other { // If we error the delegator does not get removed assert_eq!(Delegators::::get(&11), Some(delegator)); // and the sub pools do not get updated. - assert_eq!(SubPoolsStorage::::get(123).unwrap(), sub_pools) + assert_eq!(SubPoolsStorage::::get(1).unwrap(), sub_pools) }); } From d78b16a4a890056b2484bc2b270cae4c26e73197 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:53:07 -0700 Subject: [PATCH 265/299] Fix check for having enough balance to create the pool --- frame/nomination-pools/src/lib.rs | 7 ------- frame/nomination-pools/src/tests.rs | 8 -------- 2 files changed, 15 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index e86aa137cfc44..0f18a4ecf7d69 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1102,8 +1102,6 @@ pub mod pallet { /// Some error occurred that should never happen. This should be reported to the /// maintainers. DefensiveError, - /// The caller has insufficient balance to create the pool. - InsufficientBalanceToCreate, } #[pallet::call] @@ -1507,11 +1505,6 @@ pub mod pallet { Error::::MaxPools ); ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); - ensure!( - T::Currency::free_balance(&who) >= - amount.saturating_add(T::Currency::minimum_balance()), - Error::::InsufficientBalanceToCreate - ); let pool_id = LastPoolId::::mutate(|id| { *id += 1; diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index e1b96eb4a06d9..8c906bfcfa36e 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -2278,14 +2278,6 @@ mod create { assert_eq!(Delegators::::count(), 1); MaxPools::::put(3); MaxDelegators::::put(1); - - // Then - assert_noop!( - Pools::create(Origin::signed(11), 20, 11, 11, 11), - Error::::InsufficientBalanceToCreate - ); - - // Given Balances::make_free_balance_be(&11, 5 + 20); // Then From 66c5c49c623d9d192f3b87d7968ca588a60e8cb7 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Wed, 13 Apr 2022 13:18:03 -0700 Subject: [PATCH 266/299] Bond event for pool creation --- frame/nomination-pools/src/lib.rs | 7 ++++++- frame/nomination-pools/src/tests.rs | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 0f18a4ecf7d69..b8ca086c2dd23 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1544,7 +1544,12 @@ pub mod pallet { }, ); ReversePoolIdLookup::::insert(bonded_pool.bonded_account(), pool_id); - Self::deposit_event(Event::::Created { depositor: who, pool_id }); + Self::deposit_event( + Event::::Created { depositor: who.clone(), pool_id: pool_id.clone() } + ); + Self::deposit_event( + Event::::Bonded { delegator: who, pool_id, bonded: amount, joined: true } + ); bonded_pool.put(); Ok(()) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 8c906bfcfa36e..4e66af91e3b75 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -2503,6 +2503,7 @@ mod bond_extra { pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { delegator: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { delegator: 10, pool_id: 1, bonded: 10, joined: false } ] ); @@ -2548,6 +2549,7 @@ mod bond_extra { pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { delegator: 10, pool_id: 1, bonded: 10, joined: true }, Event::PaidOut { delegator: 10, pool_id: 1, payout: claimable_reward }, Event::Bonded { delegator: 10, @@ -2601,6 +2603,7 @@ mod bond_extra { pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { delegator: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { delegator: 20, pool_id: 1, bonded: 20, joined: true }, Event::PaidOut { delegator: 10, pool_id: 1, payout: 1 }, Event::Bonded { delegator: 10, pool_id: 1, bonded: 1, joined: false }, From e6f19b866f70309cd074950f7ff2e6b1aab59dc8 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 16:31:21 -0700 Subject: [PATCH 267/299] Ok to be open --- frame/nomination-pools/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index b8ca086c2dd23..ac1db80d580fb 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1179,9 +1179,10 @@ pub mod pallet { let (mut delegator, mut bonded_pool, mut reward_pool) = Self::get_delegator_with_pools(&who)?; + let (points_issued, bonded) = match extra { - BondExtra::FreeBalance(amount) => - (bonded_pool.try_bond_funds(&who, amount, BondType::Later)?, amount), + BondExtra::FreeBalance(amount) => + (bonded_pool.try_bond_funds(&who, amount, BondType::Later)?, amount), BondExtra::Rewards => { let claimed = Self::do_reward_payout( &who, @@ -1192,6 +1193,7 @@ pub mod pallet { (bonded_pool.try_bond_funds(&who, claimed, BondType::Later)?, claimed) }, }; + bonded_pool.ok_to_be_open(bonded)?; delegator.points = delegator.points.saturating_add(points_issued); Self::deposit_event(Event::::Bonded { From 8639dbfab929f048aa9a84b2707466fa0e9b0cde Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 16:34:49 -0700 Subject: [PATCH 268/299] FMT --- frame/nomination-pools/src/lib.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index ac1db80d580fb..3ccec3b16bd8f 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1179,10 +1179,9 @@ pub mod pallet { let (mut delegator, mut bonded_pool, mut reward_pool) = Self::get_delegator_with_pools(&who)?; - let (points_issued, bonded) = match extra { - BondExtra::FreeBalance(amount) => - (bonded_pool.try_bond_funds(&who, amount, BondType::Later)?, amount), + BondExtra::FreeBalance(amount) => + (bonded_pool.try_bond_funds(&who, amount, BondType::Later)?, amount), BondExtra::Rewards => { let claimed = Self::do_reward_payout( &who, @@ -1546,12 +1545,16 @@ pub mod pallet { }, ); ReversePoolIdLookup::::insert(bonded_pool.bonded_account(), pool_id); - Self::deposit_event( - Event::::Created { depositor: who.clone(), pool_id: pool_id.clone() } - ); - Self::deposit_event( - Event::::Bonded { delegator: who, pool_id, bonded: amount, joined: true } - ); + Self::deposit_event(Event::::Created { + depositor: who.clone(), + pool_id: pool_id.clone(), + }); + Self::deposit_event(Event::::Bonded { + delegator: who, + pool_id, + bonded: amount, + joined: true, + }); bonded_pool.put(); Ok(()) From a7702041f68e72fee75c3f656f681a5531eb7db6 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 16:54:18 -0700 Subject: [PATCH 269/299] Remove _other postfix --- .../nomination-pools/benchmarking/src/lib.rs | 18 +- frame/nomination-pools/src/lib.rs | 42 ++--- frame/nomination-pools/src/tests.rs | 160 +++++++++--------- frame/nomination-pools/src/weights.rs | 24 +-- 4 files changed, 122 insertions(+), 122 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 8c3948c5a475b..e4867cf7cfa78 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -282,7 +282,7 @@ frame_benchmarking::benchmarks! { ); } - unbond_other { + 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 = BalanceOf::::try_from(952_994_955_240_703u128) @@ -329,7 +329,7 @@ frame_benchmarking::benchmarks! { assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); // Unbond the new delegator - Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + Pools::::unbond(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); // Sanity check that unbond worked assert_eq!( @@ -351,7 +351,7 @@ frame_benchmarking::benchmarks! { assert_eq!(pallet_staking::Ledger::::get(pool_account).unwrap().unlocking.len(), 0); } - withdraw_unbonded_other_update { + withdraw_unbonded_update { let s in 0 .. MAX_SPANS; let min_create_bond = MinCreateBond::::get() @@ -374,7 +374,7 @@ frame_benchmarking::benchmarks! { // Unbond the new delegator pallet_staking::CurrentEra::::put(0); - Pools::::unbond_other(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + Pools::::unbond(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); // Sanity check that unbond worked assert_eq!( @@ -388,7 +388,7 @@ frame_benchmarking::benchmarks! { pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); whitelist_account!(joiner); - }: withdraw_unbonded_other(Origin::Signed(joiner.clone()), joiner.clone(), s) + }: withdraw_unbonded(Origin::Signed(joiner.clone()), joiner.clone(), s) verify { assert_eq!( CurrencyOf::::free_balance(&joiner), @@ -398,7 +398,7 @@ frame_benchmarking::benchmarks! { assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 0); } - withdraw_unbonded_other_kill { + withdraw_unbonded_kill { let s in 0 .. MAX_SPANS; let min_create_bond = MinCreateBond::::get() @@ -423,7 +423,7 @@ frame_benchmarking::benchmarks! { // up when unbonding. let reward_account = Pools::::create_reward_account(1); assert!(frame_system::Account::::contains_key(&reward_account)); - Pools::::unbond_other(Origin::Signed(depositor.clone()).into(), depositor.clone()).unwrap(); + Pools::::unbond(Origin::Signed(depositor.clone()).into(), depositor.clone()).unwrap(); // Sanity check that unbond worked assert_eq!( @@ -448,7 +448,7 @@ frame_benchmarking::benchmarks! { assert!(frame_system::Account::::contains_key(&reward_account)); whitelist_account!(depositor); - }: withdraw_unbonded_other(Origin::Signed(depositor.clone()), depositor.clone(), s) + }: withdraw_unbonded(Origin::Signed(depositor.clone()), depositor.clone(), s) verify { // Pool removal worked assert!(!pallet_staking::Ledger::::contains_key(&pool_account)); @@ -553,7 +553,7 @@ frame_benchmarking::benchmarks! { ); } - set_state_other { + set_state { // Create a pool let min_create_bond = MinCreateBond::::get() .max(T::StakingInterface::minimum_bond()) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 3ccec3b16bd8f..0181b2cb2971e 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -55,11 +55,11 @@ //! //! In order to leave, a delegator must take two steps. //! -//! First, they must call [`Call::unbond_other`]. The unbond other extrinsic will start the +//! First, they must call [`Call::unbond`]. The unbond other extrinsic will start the //! unbonding process by unbonding all of the delegators funds. //! //! Second, once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the delegator -//! can call [`Call::withdraw_unbonded_other`] to withdraw all their funds. +//! can call [`Call::withdraw_unbonded`] to withdraw all their funds. //! //! For design docs see the [bonded pool](#bonded-pool) and [unbonding sub //! pools](#unbonding-sub-pools) sections. @@ -84,7 +84,7 @@ //! * Open: Anyone can join the pool and no delegators can be permissionlessly removed. //! * Blocked: No delegators can join and some admin roles can kick delegators. //! * Destroying: No delegators can join and all delegators can be permissionlessly removed with -//! [`Call::unbond_other`] and [`Call::withdraw_unbonded_other`]. Once a pool is in destroying +//! [`Call::unbond`] and [`Call::withdraw_unbonded`]. Once a pool is in destroying //! state, it cannot be reverted to another state. //! //! A pool has 3 administrative roles (see [`PoolRoles`]): @@ -262,8 +262,8 @@ //! //! **Relevant extrinsics:** //! -//! * [`Call::unbond_other`] -//! * [`Call::withdraw_unbonded_other`] +//! * [`Call::unbond`] +//! * [`Call::withdraw_unbonded`] //! //! ### Slashing //! @@ -615,7 +615,7 @@ impl BondedPool { Ok(()) } - fn ok_to_unbond_other_with( + fn ok_to_unbond_with( &self, caller: &T::AccountId, target_account: &T::AccountId, @@ -648,12 +648,12 @@ impl BondedPool { /// # Returns /// - /// * Ok(true) if [`Call::withdraw_unbonded_other`] can be called and the target account is the + /// * Ok(true) if [`Call::withdraw_unbonded`] can be called and the target account is the /// depositor. - /// * Ok(false) if [`Call::withdraw_unbonded_other`] can be called and target account is *not* + /// * Ok(false) if [`Call::withdraw_unbonded`] can be called and target account is *not* /// the depositor. - /// * Err(DispatchError) if [`Call::withdraw_unbonded_other`] *cannot* be called. - fn ok_to_withdraw_unbonded_other_with( + /// * Err(DispatchError) if [`Call::withdraw_unbonded`] *cannot* be called. + fn ok_to_withdraw_unbonded_with( &self, caller: &T::AccountId, target_account: &T::AccountId, @@ -1252,9 +1252,9 @@ pub mod pallet { /// [`Call::pool_withdraw_unbonded`] can be called to try and minimize unlocking chunks. If /// there are too many unlocking chunks, the result of this call will likely be the /// `NoMoreChunks` error from the staking system. - #[pallet::weight(T::WeightInfo::unbond_other())] + #[pallet::weight(T::WeightInfo::unbond())] #[transactional] - pub fn unbond_other( + pub fn unbond( origin: OriginFor, delegator_account: T::AccountId, ) -> DispatchResult { @@ -1262,7 +1262,7 @@ pub mod pallet { let (mut delegator, mut bonded_pool, mut reward_pool) = Self::get_delegator_with_pools(&delegator_account)?; - bonded_pool.ok_to_unbond_other_with(&caller, &delegator_account, &delegator)?; + bonded_pool.ok_to_unbond_with(&caller, &delegator_account, &delegator)?; // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the bonded pool and thus they can no longer claim their payouts. @@ -1324,7 +1324,7 @@ pub mod pallet { /// Call `withdraw_unbonded` for the pools account. This call can be made by any account. /// - /// This is useful if their are too many unlocking chunks to call `unbond_other`, and some + /// This is useful if their are too many unlocking chunks to call `unbond`, and some /// can be cleared by withdrawing. In the case there are too many unlocking chunks, the user /// would probably see an error like `NoMoreChunks` emitted from the staking system when /// they attempt to unbond. @@ -1338,7 +1338,7 @@ pub mod pallet { let _ = ensure_signed(origin)?; let pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; // For now we only allow a pool to withdraw unbonded if its not destroying. If the pool - // is destroying then `withdraw_unbonded_other` can be used. + // is destroying then `withdraw_unbonded` can be used. ensure!(pool.state != PoolState::Destroying, Error::::NotDestroying); T::StakingInterface::withdraw_unbonded(pool.bonded_account(), num_slashing_spans)?; Ok(()) @@ -1361,10 +1361,10 @@ pub mod pallet { /// /// If the target is the depositor, the pool will be destroyed. #[pallet::weight( - T::WeightInfo::withdraw_unbonded_other_kill(*num_slashing_spans) + T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans) )] #[transactional] - pub fn withdraw_unbonded_other( + pub fn withdraw_unbonded( origin: OriginFor, delegator_account: T::AccountId, num_slashing_spans: u32, @@ -1380,7 +1380,7 @@ pub mod pallet { .defensive_ok_or_else(|| Error::::SubPoolsNotFound)?; let bonded_pool = BondedPool::::get(delegator.pool_id) .defensive_ok_or_else(|| Error::::PoolNotFound)?; - let should_remove_pool = bonded_pool.ok_to_withdraw_unbonded_other_with( + let should_remove_pool = bonded_pool.ok_to_withdraw_unbonded_with( &caller, &delegator_account, &delegator, @@ -1458,7 +1458,7 @@ pub mod pallet { } else { bonded_pool.dec_delegators().put(); SubPoolsStorage::::insert(&delegator.pool_id, sub_pools); - Some(T::WeightInfo::withdraw_unbonded_other_update(num_slashing_spans)) + Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) }; Delegators::::remove(&delegator_account); @@ -1573,8 +1573,8 @@ pub mod pallet { Ok(()) } - #[pallet::weight(T::WeightInfo::set_state_other())] - pub fn set_state_other( + #[pallet::weight(T::WeightInfo::set_state())] + pub fn set_state( origin: OriginFor, pool_id: PoolId, state: PoolState, diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 4e66af91e3b75..4a905b999651e 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1374,10 +1374,10 @@ mod unbond { use super::*; #[test] - fn unbond_other_of_1_works() { + fn unbond_of_1_works() { ExtBuilder::default().build_and_execute(|| { unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); + assert_ok!(Pools::unbond(Origin::signed(10), 10)); assert_eq!( SubPoolsStorage::::get(1).unwrap().with_era, @@ -1402,7 +1402,7 @@ mod unbond { } #[test] - fn unbond_other_of_3_works() { + fn unbond_of_3_works() { ExtBuilder::default() .add_delegators(vec![(40, 40), (550, 550)]) .build_and_execute(|| { @@ -1413,7 +1413,7 @@ mod unbond { Balances::make_free_balance_be(&default_reward_account(), ed + 600); // When - assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); + assert_ok!(Pools::unbond(Origin::signed(40), 40)); // Then assert_eq!( @@ -1439,7 +1439,7 @@ mod unbond { // When unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); + assert_ok!(Pools::unbond(Origin::signed(550), 550)); // Then assert_eq!( @@ -1463,7 +1463,7 @@ mod unbond { assert_eq!(Balances::free_balance(&550), 550 + 550); // When - assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); + assert_ok!(Pools::unbond(Origin::signed(10), 10)); // Then assert_eq!( @@ -1489,7 +1489,7 @@ mod unbond { } #[test] - fn unbond_other_merges_older_pools() { + fn unbond_merges_older_pools() { ExtBuilder::default().with_check(1).build_and_execute(|| { // Given assert_eq!(StakingMock::bonding_duration(), 3); @@ -1510,7 +1510,7 @@ mod unbond { let current_era = 1 + TotalUnbondingPools::::get(); CurrentEra::set(current_era); - assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); + assert_ok!(Pools::unbond(Origin::signed(10), 10)); // Then assert_eq!( @@ -1527,7 +1527,7 @@ mod unbond { } #[test] - fn unbond_other_kick_works() { + fn unbond_kick_works() { // Kick: the pool is blocked and the caller is either the root or state-toggler. ExtBuilder::default() .add_delegators(vec![(100, 100), (200, 200)]) @@ -1541,15 +1541,15 @@ mod unbond { // When the nominator trys to kick, then its a noop assert_noop!( - Pools::unbond_other(Origin::signed(901), 100), + Pools::unbond(Origin::signed(901), 100), Error::::NotKickerOrDestroying ); // When the root kicks then its ok - assert_ok!(Pools::unbond_other(Origin::signed(900), 100)); + assert_ok!(Pools::unbond(Origin::signed(900), 100)); // When the state toggler kicks then its ok - assert_ok!(Pools::unbond_other(Origin::signed(902), 200)); + assert_ok!(Pools::unbond(Origin::signed(902), 200)); assert_eq!( BondedPool::::get(1).unwrap(), @@ -1582,7 +1582,7 @@ mod unbond { } #[test] - fn unbond_other_with_non_admins_works() { + fn unbond_with_non_admins_works() { // Scenarios where non-admin accounts can unbond others ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given the pool is blocked @@ -1590,7 +1590,7 @@ mod unbond { // A permissionless unbond attempt errors assert_noop!( - Pools::unbond_other(Origin::signed(420), 100), + Pools::unbond(Origin::signed(420), 100), Error::::NotKickerOrDestroying ); @@ -1599,19 +1599,19 @@ mod unbond { // The depositor cannot be unbonded until they are the last delegator assert_noop!( - Pools::unbond_other(Origin::signed(420), 10), + Pools::unbond(Origin::signed(420), 10), Error::::NotOnlyDelegator ); // Any account can unbond a delegator that is not the depositor - assert_ok!(Pools::unbond_other(Origin::signed(420), 100)); + assert_ok!(Pools::unbond(Origin::signed(420), 100)); // Given the pool is blocked unsafe_set_state(1, PoolState::Blocked).unwrap(); // The depositor cannot be unbonded assert_noop!( - Pools::unbond_other(Origin::signed(420), 10), + Pools::unbond(Origin::signed(420), 10), Error::::NotDestroying ); @@ -1619,7 +1619,7 @@ mod unbond { unsafe_set_state(1, PoolState::Destroying).unwrap(); // The depositor can be unbonded - assert_ok!(Pools::unbond_other(Origin::signed(420), 10)); + assert_ok!(Pools::unbond(Origin::signed(420), 10)); assert_eq!(BondedPools::::get(1).unwrap().points, 0); assert_eq!( @@ -1645,7 +1645,7 @@ mod unbond { fn unbond_errors_correctly() { ExtBuilder::default().build_and_execute(|| { assert_noop!( - Pools::unbond_other(Origin::signed(11), 11), + Pools::unbond(Origin::signed(11), 11), Error::::DelegatorNotFound ); @@ -1658,7 +1658,7 @@ mod unbond { }; Delegators::::insert(11, delegator); - let _ = Pools::unbond_other(Origin::signed(11), 11); + let _ = Pools::unbond(Origin::signed(11), 11); }); } @@ -1684,7 +1684,7 @@ mod unbond { } .put(); - let _ = Pools::unbond_other(Origin::signed(11), 11); + let _ = Pools::unbond(Origin::signed(11), 11); }); } } @@ -1713,25 +1713,25 @@ mod pool_withdraw_unbonded { } } -mod withdraw_unbonded_other { +mod withdraw_unbonded { use super::*; #[test] - fn withdraw_unbonded_other_works_against_slashed_no_era_sub_pool() { + fn withdraw_unbonded_works_against_slashed_no_era_sub_pool() { ExtBuilder::default() .add_delegators(vec![(40, 40), (550, 550)]) .build_and_execute(|| { // Given assert_eq!(StakingMock::bonding_duration(), 3); - assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); - assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); + assert_ok!(Pools::unbond(Origin::signed(550), 550)); + assert_ok!(Pools::unbond(Origin::signed(40), 40)); assert_eq!(Balances::free_balance(&default_bonded_account()), 600); let mut current_era = 1; CurrentEra::set(current_era); // In a new era, unbond the depositor unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); + assert_ok!(Pools::unbond(Origin::signed(10), 10)); let mut sub_pools = SubPoolsStorage::::get(1).unwrap(); let unbond_pool = sub_pools.with_era.get_mut(&(current_era + 3)).unwrap(); @@ -1766,7 +1766,7 @@ mod withdraw_unbonded_other { ); // When - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(550), 550, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 550, 0)); // Then assert_eq!( @@ -1778,7 +1778,7 @@ mod withdraw_unbonded_other { assert!(!Delegators::::contains_key(550)); // When - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(40), 40, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 40, 0)); // Then assert_eq!( @@ -1790,7 +1790,7 @@ mod withdraw_unbonded_other { assert!(!Delegators::::contains_key(40)); // When - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(10), 10, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); // Then assert_eq!(Balances::free_balance(&10), 5 + 10); @@ -1806,7 +1806,7 @@ mod withdraw_unbonded_other { // This test also documents the case when the pools free balance goes below ED before all // delegators have unbonded. #[test] - fn withdraw_unbonded_other_works_against_slashed_with_era_sub_pools() { + fn withdraw_unbonded_works_against_slashed_with_era_sub_pools() { ExtBuilder::default() .add_delegators(vec![(40, 40), (550, 550)]) .build_and_execute(|| { @@ -1815,10 +1815,10 @@ mod withdraw_unbonded_other { Balances::make_free_balance_be(&default_bonded_account(), 100); assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(100)); - assert_ok!(Pools::unbond_other(Origin::signed(40), 40)); - assert_ok!(Pools::unbond_other(Origin::signed(550), 550)); + assert_ok!(Pools::unbond(Origin::signed(40), 40)); + assert_ok!(Pools::unbond(Origin::signed(550), 550)); unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); + assert_ok!(Pools::unbond(Origin::signed(10), 10)); SubPoolsStorage::::insert( 1, @@ -1830,7 +1830,7 @@ mod withdraw_unbonded_other { CurrentEra::set(StakingMock::bonding_duration()); // When - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(40), 40, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 40, 0)); // Then assert_eq!( @@ -1842,7 +1842,7 @@ mod withdraw_unbonded_other { assert!(!Delegators::::contains_key(40)); // When - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(550), 550, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 550, 0)); // Then assert_eq!( @@ -1855,7 +1855,7 @@ mod withdraw_unbonded_other { assert!(!Delegators::::contains_key(550)); // When - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(10), 10, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); // Then assert_eq!(Balances::free_balance(&10), 5 + 0); @@ -1869,14 +1869,14 @@ mod withdraw_unbonded_other { } #[test] - fn withdraw_unbonded_other_handles_faulty_sub_pool_accounting() { + fn withdraw_unbonded_handles_faulty_sub_pool_accounting() { ExtBuilder::default().build_and_execute(|| { // Given assert_eq!(Balances::minimum_balance(), 5); assert_eq!(Balances::free_balance(&10), 5); assert_eq!(Balances::free_balance(&default_bonded_account()), 10); unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); + assert_ok!(Pools::unbond(Origin::signed(10), 10)); // Simulate a slash that is not accounted for in the sub pools. Balances::make_free_balance_be(&default_bonded_account(), 5); @@ -1889,7 +1889,7 @@ mod withdraw_unbonded_other { CurrentEra::set(0 + 3); // When - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(10), 10, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); // Then assert_eq!(Balances::free_balance(10), 5 + 5); @@ -1898,7 +1898,7 @@ mod withdraw_unbonded_other { } #[test] - fn withdraw_unbonded_other_errors_correctly() { + fn withdraw_unbonded_errors_correctly() { ExtBuilder::default().with_check(0).build_and_execute(|| { // Insert the sub-pool let sub_pools = SubPools { @@ -1908,7 +1908,7 @@ mod withdraw_unbonded_other { SubPoolsStorage::::insert(1, sub_pools.clone()); assert_noop!( - Pools::withdraw_unbonded_other(Origin::signed(11), 11, 0), + Pools::withdraw_unbonded(Origin::signed(11), 11, 0), Error::::DelegatorNotFound ); @@ -1922,7 +1922,7 @@ mod withdraw_unbonded_other { // The delegator has not called `unbond` assert_noop!( - Pools::withdraw_unbonded_other(Origin::signed(11), 11, 0), + Pools::withdraw_unbonded(Origin::signed(11), 11, 0), Error::::NotUnbonding ); @@ -1932,7 +1932,7 @@ mod withdraw_unbonded_other { // We are still in the bonding duration assert_noop!( - Pools::withdraw_unbonded_other(Origin::signed(11), 11, 0), + Pools::withdraw_unbonded(Origin::signed(11), 11, 0), Error::::NotUnbondedYet ); @@ -1944,13 +1944,13 @@ mod withdraw_unbonded_other { } #[test] - fn withdraw_unbonded_other_kick() { + fn withdraw_unbonded_kick() { ExtBuilder::default() .add_delegators(vec![(100, 100), (200, 200)]) .build_and_execute(|| { // Given - assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); - assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); + assert_ok!(Pools::unbond(Origin::signed(100), 100)); + assert_ok!(Pools::unbond(Origin::signed(200), 200)); assert_eq!( BondedPool::::get(1).unwrap(), BondedPool { @@ -1967,7 +1967,7 @@ mod withdraw_unbonded_other { // Cannot kick when pool is open assert_noop!( - Pools::withdraw_unbonded_other(Origin::signed(902), 100, 0), + Pools::withdraw_unbonded(Origin::signed(902), 100, 0), Error::::NotKickerOrDestroying ); @@ -1976,15 +1976,15 @@ mod withdraw_unbonded_other { // Cannot kick as a nominator assert_noop!( - Pools::withdraw_unbonded_other(Origin::signed(901), 100, 0), + Pools::withdraw_unbonded(Origin::signed(901), 100, 0), Error::::NotKickerOrDestroying ); // Can kick as root - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(900), 100, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(900), 100, 0)); // Can kick as state toggler - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(900), 200, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(900), 200, 0)); assert_eq!(Balances::free_balance(100), 100 + 100); assert_eq!(Balances::free_balance(200), 200 + 200); @@ -1995,10 +1995,10 @@ mod withdraw_unbonded_other { } #[test] - fn withdraw_unbonded_other_destroying_permissionless() { + fn withdraw_unbonded_destroying_permissionless() { ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given - assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); + assert_ok!(Pools::unbond(Origin::signed(100), 100)); assert_eq!( BondedPool::::get(1).unwrap(), BondedPool { @@ -2016,7 +2016,7 @@ mod withdraw_unbonded_other { // Cannot permissionlessly withdraw assert_noop!( - Pools::unbond_other(Origin::signed(420), 100), + Pools::unbond(Origin::signed(420), 100), Error::::NotKickerOrDestroying ); @@ -2024,7 +2024,7 @@ mod withdraw_unbonded_other { unsafe_set_state(1, PoolState::Destroying).unwrap(); // Can permissionlesly withdraw a delegator that is not the depositor - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 100, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0)); assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default(),); assert_eq!(Balances::free_balance(100), 100 + 100); @@ -2033,19 +2033,19 @@ mod withdraw_unbonded_other { } #[test] - fn withdraw_unbonded_other_depositor_with_era_pool() { + fn withdraw_unbonded_depositor_with_era_pool() { ExtBuilder::default() .add_delegators(vec![(100, 100), (200, 200)]) .build_and_execute(|| { // Given - assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); + assert_ok!(Pools::unbond(Origin::signed(100), 100)); let mut current_era = 1; CurrentEra::set(current_era); - assert_ok!(Pools::unbond_other(Origin::signed(200), 200)); + assert_ok!(Pools::unbond(Origin::signed(200), 200)); unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); + assert_ok!(Pools::unbond(Origin::signed(10), 10)); assert_eq!( SubPoolsStorage::::get(1).unwrap(), @@ -2063,12 +2063,12 @@ mod withdraw_unbonded_other { // Cannot withdraw the depositor if their is a delegator in another `with_era` pool. assert_noop!( - Pools::withdraw_unbonded_other(Origin::signed(420), 10, 0), + Pools::withdraw_unbonded(Origin::signed(420), 10, 0), Error::::NotOnlyDelegator ); // Given - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 100, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0)); assert_eq!( SubPoolsStorage::::get(1).unwrap(), SubPools { @@ -2082,12 +2082,12 @@ mod withdraw_unbonded_other { // Cannot withdraw if their is another delegator in the depositors `with_era` pool assert_noop!( - Pools::unbond_other(Origin::signed(420), 10), + Pools::unbond(Origin::signed(420), 10), Error::::NotOnlyDelegator ); // Given - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 200, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 200, 0)); assert_eq!( SubPoolsStorage::::get(1).unwrap(), SubPools { @@ -2099,7 +2099,7 @@ mod withdraw_unbonded_other { ); // The depositor can withdraw - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 10, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 10, 0)); assert!(!Delegators::::contains_key(10)); assert_eq!(Balances::free_balance(10), 10 + 10 - Balances::minimum_balance()); // Pools are removed from storage because the depositor left @@ -2110,12 +2110,12 @@ mod withdraw_unbonded_other { } #[test] - fn withdraw_unbonded_other_depositor_no_era_pool() { + fn withdraw_unbonded_depositor_no_era_pool() { ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given - assert_ok!(Pools::unbond_other(Origin::signed(100), 100)); + assert_ok!(Pools::unbond(Origin::signed(100), 100)); unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond_other(Origin::signed(10), 10)); + assert_ok!(Pools::unbond(Origin::signed(10), 10)); // Skip ahead to an era where the `with_era` pools can get merged into the `no_era` // pool. let current_era = TotalUnbondingPools::::get(); @@ -2135,12 +2135,12 @@ mod withdraw_unbonded_other { // Cannot withdraw depositor with another delegator in the `no_era` pool assert_noop!( - Pools::withdraw_unbonded_other(Origin::signed(420), 10, 0), + Pools::withdraw_unbonded(Origin::signed(420), 10, 0), Error::::NotOnlyDelegator ); // Given - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 100, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0)); assert_eq!( SubPoolsStorage::::get(1).unwrap(), SubPools { @@ -2150,7 +2150,7 @@ mod withdraw_unbonded_other { ); // The depositor can withdraw - assert_ok!(Pools::withdraw_unbonded_other(Origin::signed(420), 10, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 10, 0)); assert!(!Delegators::::contains_key(10)); assert_eq!(Balances::free_balance(10), 5 + 10); // Pools are removed from storage because the depositor left @@ -2324,40 +2324,40 @@ mod nominate { } } -mod set_state_other { +mod set_state { use super::*; #[test] - fn set_state_other_works() { + fn set_state_works() { ExtBuilder::default().build_and_execute(|| { // Given assert_ok!(BondedPool::::get(1).unwrap().ok_to_be_open(0)); // Only the root and state toggler can change the state when the pool is ok to be open. assert_noop!( - Pools::set_state_other(Origin::signed(10), 1, PoolState::Blocked), + Pools::set_state(Origin::signed(10), 1, PoolState::Blocked), Error::::CanNotChangeState ); assert_noop!( - Pools::set_state_other(Origin::signed(901), 1, PoolState::Blocked), + Pools::set_state(Origin::signed(901), 1, PoolState::Blocked), Error::::CanNotChangeState ); // Root can change state - assert_ok!(Pools::set_state_other(Origin::signed(900), 1, PoolState::Blocked)); + assert_ok!(Pools::set_state(Origin::signed(900), 1, PoolState::Blocked)); assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Blocked); // State toggler can change state - assert_ok!(Pools::set_state_other(Origin::signed(902), 1, PoolState::Destroying)); + assert_ok!(Pools::set_state(Origin::signed(902), 1, PoolState::Destroying)); assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Destroying); // If the pool is destroying, then no one can set state assert_noop!( - Pools::set_state_other(Origin::signed(900), 1, PoolState::Blocked), + Pools::set_state(Origin::signed(900), 1, PoolState::Blocked), Error::::CanNotChangeState ); assert_noop!( - Pools::set_state_other(Origin::signed(902), 1, PoolState::Blocked), + Pools::set_state(Origin::signed(902), 1, PoolState::Blocked), Error::::CanNotChangeState ); @@ -2369,7 +2369,7 @@ mod set_state_other { bonded_pool.points = 100; bonded_pool.put(); // When - assert_ok!(Pools::set_state_other(Origin::signed(11), 1, PoolState::Destroying)); + assert_ok!(Pools::set_state(Origin::signed(11), 1, PoolState::Destroying)); // Then assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Destroying); @@ -2377,7 +2377,7 @@ mod set_state_other { Balances::make_free_balance_be(&default_bonded_account(), Balance::max_value() / 10); unsafe_set_state(1, PoolState::Open).unwrap(); // When - assert_ok!(Pools::set_state_other(Origin::signed(11), 1, PoolState::Destroying)); + assert_ok!(Pools::set_state(Origin::signed(11), 1, PoolState::Destroying)); // Then assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Destroying); @@ -2385,7 +2385,7 @@ mod set_state_other { // isn't destroying unsafe_set_state(1, PoolState::Open).unwrap(); assert_noop!( - Pools::set_state_other(Origin::signed(11), 1, PoolState::Blocked), + Pools::set_state(Origin::signed(11), 1, PoolState::Blocked), Error::::CanNotChangeState ); }); diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index f29b40c8e1fc0..57f21352d6bd1 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -47,13 +47,13 @@ pub trait WeightInfo { fn bond_extra_transfer() -> Weight; fn bond_extra_reward() -> Weight; fn claim_payout() -> Weight; - fn unbond_other() -> Weight; + fn unbond() -> Weight; fn pool_withdraw_unbonded(s: u32, ) -> Weight; - fn withdraw_unbonded_other_update(s: u32, ) -> Weight; - fn withdraw_unbonded_other_kill(s: u32, ) -> Weight; + fn withdraw_unbonded_update(s: u32, ) -> Weight; + fn withdraw_unbonded_kill(s: u32, ) -> Weight; fn create() -> Weight; fn nominate(n: u32) -> Weight; - fn set_state_other() -> Weight; + fn set_state() -> Weight; fn set_metadata(_n: u32) -> Weight; fn set_configs() -> Weight; } @@ -110,7 +110,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListBags (r:2 w:2) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) - fn unbond_other() -> Weight { + fn unbond() -> Weight { (114_657_000 as Weight) .saturating_add(T::DbWeight::get().reads(18 as Weight)) .saturating_add(T::DbWeight::get().writes(13 as Weight)) @@ -133,14 +133,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForDelegators (r:1 w:1) - fn withdraw_unbonded_other_update(s: u32, ) -> Weight { + fn withdraw_unbonded_update(s: u32, ) -> Weight { (66_522_000 as Weight) // Standard Error: 0 .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } - fn withdraw_unbonded_other_kill(s: u32, ) -> Weight { + fn withdraw_unbonded_kill(s: u32, ) -> Weight { (66_522_000 as Weight) // Standard Error: 0 .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) @@ -187,7 +187,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(28 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } - fn set_state_other() -> Weight { + fn set_state() -> Weight { (79_587_000 as Weight) .saturating_add(T::DbWeight::get().reads(28 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) @@ -255,7 +255,7 @@ impl WeightInfo for () { // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListBags (r:2 w:2) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) - fn unbond_other() -> Weight { + fn unbond() -> Weight { (114_657_000 as Weight) .saturating_add(RocksDbWeight::get().reads(18 as Weight)) .saturating_add(RocksDbWeight::get().writes(13 as Weight)) @@ -278,7 +278,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForDelegators (r:1 w:1) - fn withdraw_unbonded_other_update(s: u32) -> Weight { + fn withdraw_unbonded_update(s: u32) -> Weight { (66_522_000 as Weight) // Standard Error: 0 .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) @@ -286,7 +286,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: NominationPools CounterForDelegators (r:1 w:1) - fn withdraw_unbonded_other_kill(s: u32) -> Weight { + fn withdraw_unbonded_kill(s: u32) -> Weight { (66_522_000 as Weight) // Standard Error: 0 .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) @@ -333,7 +333,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(28 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } - fn set_state_other() -> Weight { + fn set_state() -> Weight { (79_587_000 as Weight) .saturating_add(RocksDbWeight::get().reads(28 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) From 7b9f9747aa347942c2e326de8a08ba496a895881 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Thu, 14 Apr 2022 17:04:29 -0700 Subject: [PATCH 270/299] Update frame/staking/src/lib.rs --- frame/staking/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 7601fb232cd90..8003b9814c8e6 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -589,7 +589,7 @@ impl StakingLedger { // slash out from *target exactly `slash_from_target`. *target = *target - slash_from_target; - if *target <= minimum_balance { + if *target < minimum_balance { // Slash the rest of the target if its dust slash_from_target = sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) From 21202e68bf87de498af1fe8b7ef36091ff687edb Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 17:20:08 -0700 Subject: [PATCH 271/299] Adjust tests to account for only remove when < ED --- frame/staking/src/tests.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 7b75827c97e7a..d58df33f4a557 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4979,8 +4979,8 @@ fn ledger_slash_works() { // When assert_eq!( ledger.slash( - 350, // active + era 6 + era 7 + era 5 / 2 - 50, // min balance - everything slashed to 50 or below will get dusted + 351, // active + era 6 + era 7 + era 5 / 2 + 1 + 50, // min balance - everything slashed below 50 will get dusted 2 /* slash era 2+4 first, so the affected parts are era 2+4, era 3+4 and * ledge.active. This will cause the affected to go to zero, and then we will * start slashing older chunks */ @@ -4990,7 +4990,6 @@ fn ledger_slash_works() { // Then assert_eq!(ledger.active, 0); assert_eq!(ledger.unlocking, vec![c(4, 100)]); - //------goes to min balance and then gets dusted^^^ assert_eq!(ledger.total, 100); assert_eq!(LedgerSlashPerEra::get().0, 0); assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)])); From 52728c0484bc5db348cd47ecd9b575ee37553047 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 17:23:17 -0700 Subject: [PATCH 272/299] Remove stale TODOs --- frame/staking/src/tests.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index c9575cf869b2a..952994e371d10 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4868,14 +4868,13 @@ fn ledger_slash_works() { assert_eq!(LedgerSlashPerEra::get().0, 0); assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)])); - // TODO test does not slash earlier eras // Given ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; ledger.total = 4 * 100; ledger.active = 0; // When the first 2 chunks don't overlap with the affected range of unlock eras. - assert_eq!(ledger.slash(140, 0, 2), 140); // TODO - // Then + assert_eq!(ledger.slash(140, 0, 2), 140); + // Then assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]); assert_eq!(ledger.total, 4 * 100 - 140); assert_eq!(LedgerSlashPerEra::get().0, 0); From d3ef3b45ab74a8aea969811b324e8274205de4ee Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 17:35:22 -0700 Subject: [PATCH 273/299] Remove dupe test --- frame/staking/src/tests.rs | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 952994e371d10..45aec81c46520 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4935,29 +4935,6 @@ fn ledger_slash_works() { BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 250 / 2)]) ); - // Given we have the same as above, - ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; - ledger.active = 500; - ledger.total = 40 + 10 + 100 + 250 + 500; // 900 - assert_eq!( - ledger.slash( - 900 / 2, - 25, /* min balance - chunks with era 5 & 5 will be slashed to <=25, causing them to - * get swept */ - 0 - ), - 475 - ); - // Then - assert_eq!(ledger.active, 500 / 2); - assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 250 / 2)]); - assert_eq!(ledger.total, 425); - assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); - assert_eq!( - LedgerSlashPerEra::get().1, - BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 250 / 2)]) - ); - // Given // slash order --------------------NA--------2----------0----------1---- ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; From fa70ade3f210fe8d5ed4c277ff4f6a6b33e27410 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 18:07:00 -0700 Subject: [PATCH 274/299] Fix build --- frame/staking/src/mock.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index ad77131e360f5..e39691a3b6d7e 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -292,7 +292,6 @@ impl crate::pallet::pallet::Config for Test { type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = OnStakerSlashMock; type BenchmarkingConfig = TestBenchmarkingConfig; - type OnStakerSlash = (); type WeightInfo = (); } From 9def21acd48530e1c9df5e98658023e2f29589e1 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 18:32:53 -0700 Subject: [PATCH 275/299] Make sure to convert to u256 so we don't saturate --- frame/nomination-pools/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 0181b2cb2971e..789757dc934be 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1725,6 +1725,8 @@ impl Pallet { current_points: BalanceOf, new_funds: BalanceOf, ) -> BalanceOf { + let u256 = |x| T::BalanceToU256::convert(x); + let balance = |x| T::U256ToBalance::convert(x); match (current_balance.is_zero(), current_points.is_zero()) { (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { @@ -1734,10 +1736,10 @@ impl Pallet { }, (false, false) => { // Equivalent to (current_points / current_balance) * new_funds - current_points - .saturating_mul(new_funds) + balance(u256(current_points) + .saturating_mul(u256(new_funds)) // We check for zero above - .div(current_balance) + .div(u256(current_balance))) }, } } From 9a35ad614fe32bbcd21a862b5a89381da6677dc9 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 19:37:34 -0700 Subject: [PATCH 276/299] Refund depositor with reward pool fee --- frame/nomination-pools/src/lib.rs | 34 ++++++++++++++++++----------- frame/nomination-pools/src/tests.rs | 15 +++++-------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 789757dc934be..ab5839147d97b 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -84,8 +84,8 @@ //! * Open: Anyone can join the pool and no delegators can be permissionlessly removed. //! * Blocked: No delegators can join and some admin roles can kick delegators. //! * Destroying: No delegators can join and all delegators can be permissionlessly removed with -//! [`Call::unbond`] and [`Call::withdraw_unbonded`]. Once a pool is in destroying -//! state, it cannot be reverted to another state. +//! [`Call::unbond`] and [`Call::withdraw_unbonded`]. Once a pool is in destroying state, it +//! cannot be reverted to another state. //! //! A pool has 3 administrative roles (see [`PoolRoles`]): //! @@ -650,8 +650,8 @@ impl BondedPool { /// /// * Ok(true) if [`Call::withdraw_unbonded`] can be called and the target account is the /// depositor. - /// * Ok(false) if [`Call::withdraw_unbonded`] can be called and target account is *not* - /// the depositor. + /// * Ok(false) if [`Call::withdraw_unbonded`] can be called and target account is *not* the + /// depositor. /// * Err(DispatchError) if [`Call::withdraw_unbonded`] *cannot* be called. fn ok_to_withdraw_unbonded_with( &self, @@ -1254,10 +1254,7 @@ pub mod pallet { /// `NoMoreChunks` error from the staking system. #[pallet::weight(T::WeightInfo::unbond())] #[transactional] - pub fn unbond( - origin: OriginFor, - delegator_account: T::AccountId, - ) -> DispatchResult { + pub fn unbond(origin: OriginFor, delegator_account: T::AccountId) -> DispatchResult { let caller = ensure_signed(origin)?; let (mut delegator, mut bonded_pool, mut reward_pool) = Self::get_delegator_with_pools(&delegator_account)?; @@ -1439,6 +1436,7 @@ pub mod pallet { }); let post_info_weight = if should_remove_pool { + let reward_account = bonded_pool.reward_account(); ReversePoolIdLookup::::remove(bonded_pool.bonded_account()); RewardPools::::remove(delegator.pool_id); Self::deposit_event(Event::::Destroyed { pool_id: delegator.pool_id }); @@ -1451,7 +1449,15 @@ pub mod pallet { .unwrap_or_default(), Zero::zero() ); - T::Currency::make_free_balance_be(&bonded_pool.reward_account(), Zero::zero()); + let reward_pool_remaining = T::Currency::free_balance(&reward_account); + // This shouldn't fail, but if it does we don't really care + let _ = T::Currency::transfer( + &reward_account, + &delegator_account, + reward_pool_remaining, + ExistenceRequirement::AllowDeath, + ); + T::Currency::make_free_balance_be(&reward_account, Zero::zero()); T::Currency::make_free_balance_be(&bonded_pool.bonded_account(), Zero::zero()); bonded_pool.remove(); None @@ -1736,10 +1742,12 @@ impl Pallet { }, (false, false) => { // Equivalent to (current_points / current_balance) * new_funds - balance(u256(current_points) - .saturating_mul(u256(new_funds)) - // We check for zero above - .div(u256(current_balance))) + balance( + u256(current_points) + .saturating_mul(u256(new_funds)) + // We check for zero above + .div(u256(current_balance)), + ) }, } } diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 4a905b999651e..134d344b99bc9 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1610,10 +1610,7 @@ mod unbond { unsafe_set_state(1, PoolState::Blocked).unwrap(); // The depositor cannot be unbonded - assert_noop!( - Pools::unbond(Origin::signed(420), 10), - Error::::NotDestroying - ); + assert_noop!(Pools::unbond(Origin::signed(420), 10), Error::::NotDestroying); // Given the pools is destroying unsafe_set_state(1, PoolState::Destroying).unwrap(); @@ -1793,7 +1790,7 @@ mod withdraw_unbonded { assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); // Then - assert_eq!(Balances::free_balance(&10), 5 + 10); + assert_eq!(Balances::free_balance(&10), 10 + 10); assert_eq!(Balances::free_balance(&default_bonded_account()), 0); assert!(!Delegators::::contains_key(10)); // Pools are removed from storage because the depositor left @@ -1858,7 +1855,7 @@ mod withdraw_unbonded { assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); // Then - assert_eq!(Balances::free_balance(&10), 5 + 0); + assert_eq!(Balances::free_balance(&10), 10 + 0); assert_eq!(Balances::free_balance(&default_bonded_account()), 0); assert!(!Delegators::::contains_key(10)); // Pools are removed from storage because the depositor left @@ -1892,7 +1889,7 @@ mod withdraw_unbonded { assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); // Then - assert_eq!(Balances::free_balance(10), 5 + 5); + assert_eq!(Balances::free_balance(10), 10 + 5); assert_eq!(Balances::free_balance(&default_bonded_account()), 0); }); } @@ -2101,7 +2098,7 @@ mod withdraw_unbonded { // The depositor can withdraw assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 10, 0)); assert!(!Delegators::::contains_key(10)); - assert_eq!(Balances::free_balance(10), 10 + 10 - Balances::minimum_balance()); + assert_eq!(Balances::free_balance(10), 10 + 10); // Pools are removed from storage because the depositor left assert!(!SubPoolsStorage::::contains_key(1)); assert!(!RewardPools::::contains_key(1)); @@ -2152,7 +2149,7 @@ mod withdraw_unbonded { // The depositor can withdraw assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 10, 0)); assert!(!Delegators::::contains_key(10)); - assert_eq!(Balances::free_balance(10), 5 + 10); + assert_eq!(Balances::free_balance(10), 10 + 10); // Pools are removed from storage because the depositor left assert!(!SubPoolsStorage::::contains_key(1)); assert!(!RewardPools::::contains_key(1)); From 7efaee865b128359a3f476f2071b1848693d8166 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 19:45:14 -0700 Subject: [PATCH 277/299] FMT --- frame/nomination-pools/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index ab5839147d97b..862d967a868ca 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -562,8 +562,8 @@ impl BondedPool { } fn can_kick(&self, who: &T::AccountId) -> bool { - *who == self.roles.root || - *who == self.roles.state_toggler && self.state == PoolState::Blocked + (*who == self.roles.root || *who == self.roles.state_toggler) && + self.state == PoolState::Blocked } fn can_toggle_state(&self, who: &T::AccountId) -> bool { From d06b49cb2d8b88b9d0e73657d8ee3cc97ded50db Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 19:50:40 -0700 Subject: [PATCH 278/299] Remove reachable defensive --- frame/nomination-pools/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 862d967a868ca..374276a9b4b1e 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1530,8 +1530,7 @@ pub mod pallet { &bonded_pool.reward_account(), T::Currency::minimum_balance(), ExistenceRequirement::AllowDeath, - ) - .defensive()?; + )?; Delegators::::insert( who.clone(), From 3975eca6f5340168168cfbca24c894c620ea31fe Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 19:57:42 -0700 Subject: [PATCH 279/299] Use compact encoding for relevant extrinsics --- frame/nomination-pools/src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 374276a9b4b1e..c58f93e879823 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1118,7 +1118,11 @@ pub mod pallet { /// * Only a pool with [`PoolState::Open`] can be joined #[pallet::weight(T::WeightInfo::join())] #[transactional] - pub fn join(origin: OriginFor, amount: BalanceOf, pool_id: PoolId) -> DispatchResult { + pub fn join( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + pool_id: PoolId, + ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); @@ -1492,7 +1496,7 @@ pub mod pallet { #[transactional] pub fn create( origin: OriginFor, - amount: BalanceOf, + #[pallet::compact] amount: BalanceOf, root: T::AccountId, nominator: T::AccountId, state_toggler: T::AccountId, From 7b46bdd65809ab7a068bd90f5174105352a1a5df Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 20:02:49 -0700 Subject: [PATCH 280/299] Remove unnescary make_free_be for cleaning reward account --- frame/nomination-pools/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index c58f93e879823..f12edf33d862c 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1461,7 +1461,6 @@ pub mod pallet { reward_pool_remaining, ExistenceRequirement::AllowDeath, ); - T::Currency::make_free_balance_be(&reward_account, Zero::zero()); T::Currency::make_free_balance_be(&bonded_pool.bonded_account(), Zero::zero()); bonded_pool.remove(); None From 5e183c112b9ff917cbb70814b944f2eace9cdb67 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 14 Apr 2022 20:07:36 -0700 Subject: [PATCH 281/299] Add not to maintainers for reward account accounting --- frame/nomination-pools/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index f12edf33d862c..97d3626958fa6 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -216,6 +216,11 @@ //! because it's vote weight will not be counted until the election snapshot in active era + 1. //! Related: //! +//! _Note to maintainers_: In order to ensure the reward account never falls below the existential +//! deposit, at creation the reward account must be endowed with the existential deposit. All logic +//! for calculating rewards then does not see that existential deposit as part of the free balance. +//! See `RewardPool::current_balance`. +//! //! **Relevant extrinsics:** //! //! * [`Call::claim_payout`] From d33253c367df38b9c42cbc14722ef8e48ce516f6 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 15 Apr 2022 16:29:39 -0700 Subject: [PATCH 282/299] Remove note to maintainers from public doc --- frame/nomination-pools/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 97d3626958fa6..4dc2535a20d5f 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -216,10 +216,10 @@ //! because it's vote weight will not be counted until the election snapshot in active era + 1. //! Related: //! -//! _Note to maintainers_: In order to ensure the reward account never falls below the existential -//! deposit, at creation the reward account must be endowed with the existential deposit. All logic -//! for calculating rewards then does not see that existential deposit as part of the free balance. -//! See `RewardPool::current_balance`. +// _Note to maintainers_: In order to ensure the reward account never falls below the existential +// deposit, at creation the reward account must be endowed with the existential deposit. All logic +// for calculating rewards then does not see that existential deposit as part of the free balance. +// See `RewardPool::current_balance`. //! //! **Relevant extrinsics:** //! From 1cd80f325c8674a13d9d39589a87708fa95fdc01 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:07:15 -0700 Subject: [PATCH 283/299] Make sure all configs have currency balance --- frame/nomination-pools/benchmarking/src/lib.rs | 2 +- frame/nomination-pools/benchmarking/src/mock.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index e4867cf7cfa78..c2d118113b60b 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -463,7 +463,7 @@ frame_benchmarking::benchmarks! { assert_eq!( CurrencyOf::::free_balance(&depositor), // gets bond back + rewards collecting when unbonding - min_create_bond * 2u32.into() + min_create_bond * 2u32.into() + CurrencyOf::::minimum_balance() ); } diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 3bc4f72d472f9..b6a43471b8355 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -89,6 +89,7 @@ parameter_types! { 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 = (); From 844f42fc90274ecb102c09f568f1077d8e4e180f Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Tue, 19 Apr 2022 13:12:25 -0700 Subject: [PATCH 284/299] Avoid saturation in balance_to_unbond --- frame/nomination-pools/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 4dc2535a20d5f..5b2c206d89031 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -215,7 +215,6 @@ //! after the election snapshot is taken it will benefit from the rewards of the next _2_ eras //! because it's vote weight will not be counted until the election snapshot in active era + 1. //! Related: -//! // _Note to maintainers_: In order to ensure the reward account never falls below the existential // deposit, at creation the reward account must be endowed with the existential deposit. All logic // for calculating rewards then does not see that existential deposit as part of the free balance. @@ -1766,14 +1765,15 @@ impl Pallet { current_points: BalanceOf, delegator_points: BalanceOf, ) -> BalanceOf { + let u256 = |x| T::BalanceToU256::convert(x); + let balance = |x| T::U256ToBalance::convert(x); if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { // There is nothing to unbond return Zero::zero() } // Equivalent of (current_balance / current_points) * delegator_points - current_balance - .saturating_mul(delegator_points) + balance(u256(current_balance).saturating_mul(u256(delegator_points))) // We check for zero above .div(current_points) } From 781fb6ebb24d1128672ebcf475d3c00fda7fadc8 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 20 Apr 2022 09:18:22 +0100 Subject: [PATCH 285/299] Partial Unbonding for Nomination Pools (#11212) * first draft of partial unbonding for pools * remove option * Add some more tests and fix issues * Fix all tests * simplify some tests * Update frame/nomination-pools/src/mock.rs * remove clone * rename to delegator_unbonding_eras * Update frame/nomination-pools/src/tests.rs Co-authored-by: Zeke Mostov * Update frame/nomination-pools/src/tests.rs Co-authored-by: Zeke Mostov * Update frame/nomination-pools/src/tests.rs Co-authored-by: Zeke Mostov * remove pub * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * undo * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * leftovers * fix invariant * Fix the depositor assumption * round of self-review * little bit more cleanup * Update frame/nomination-pools/src/mock.rs * Apply suggestions from code review * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * Fix interpretation of MinCreateBond * controvesial refactor * rename * make everything build * add TODO about killing the reward account * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * Update frame/nomination-pools/src/lib.rs * last self-review Co-authored-by: Zeke Mostov --- bin/node/runtime/src/lib.rs | 1 + frame/nomination-pools/Cargo.toml | 1 + .../nomination-pools/benchmarking/Cargo.toml | 2 +- .../nomination-pools/benchmarking/src/lib.rs | 19 +- .../nomination-pools/benchmarking/src/mock.rs | 1 + frame/nomination-pools/src/lib.rs | 548 ++++++++++----- frame/nomination-pools/src/mock.rs | 21 +- frame/nomination-pools/src/tests.rs | 663 ++++++++++++++---- frame/support/src/lib.rs | 21 + 9 files changed, 945 insertions(+), 332 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index b9b5c29f82694..30197ec718337 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -731,6 +731,7 @@ impl pallet_nomination_pools::Config for Runtime { type StakingInterface = pallet_staking::Pallet; type PostUnbondingPoolsWindow = PostUnbondPoolsWindow; type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = ConstU32<8>; type PalletId = NominationPoolsPalletId; } diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 6565e695901e2..5e485e62f21d9 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -30,6 +30,7 @@ pallet-balances = { version = "4.0.0-dev", path = "../balances" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } [features] +runtime-benchmarks = [] default = ["std"] std = [ "codec/std", diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index 700e626e79ae0..8416216d12e94 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -24,7 +24,7 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../.. frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } pallet-bags-list = { version = "4.0.0-dev", default-features = false, features = ["runtime-benchmarks"], path = "../../bags-list" } pallet-staking = { version = "4.0.0-dev", default-features = false, features = ["runtime-benchmarks"], path = "../../staking" } -pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../" } +pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../", features = ["runtime-benchmarks"] } # Substrate Primitives sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index c2d118113b60b..50dd97d2cd1a5 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -289,13 +289,13 @@ frame_benchmarking::benchmarks! { .map_err(|_| "balance expected to be a u128") .unwrap(); let scenario = ListScenario::::new(origin_weight, false)?; - let amount = origin_weight - scenario.dest_weight.clone(); let scenario = scenario.add_joiner(amount); let delegator_id = scenario.origin1_delegator.unwrap().clone(); + let all_points = Delegators::::get(&delegator_id).unwrap().points; whitelist_account!(delegator_id); - }: _(Origin::Signed(delegator_id.clone()), delegator_id.clone()) + }: _(Origin::Signed(delegator_id.clone()), delegator_id.clone(), all_points) verify { let bonded_after = T::StakingInterface::active_stake(&scenario.origin1).unwrap(); // We at least went down to the destination bag @@ -304,7 +304,14 @@ frame_benchmarking::benchmarks! { &delegator_id ) .unwrap(); - assert_eq!(delegator.unbonding_era, Some(0 + T::StakingInterface::bonding_duration())); + assert_eq!( + delegator.unbonding_eras.keys().cloned().collect::>(), + vec![0 + T::StakingInterface::bonding_duration()] + ); + assert_eq!( + delegator.unbonding_eras.values().cloned().collect::>(), + vec![all_points] + ); } pool_withdraw_unbonded { @@ -329,7 +336,7 @@ frame_benchmarking::benchmarks! { assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); // Unbond the new delegator - Pools::::unbond(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + Pools::::fully_unbond(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); // Sanity check that unbond worked assert_eq!( @@ -374,7 +381,7 @@ frame_benchmarking::benchmarks! { // Unbond the new delegator pallet_staking::CurrentEra::::put(0); - Pools::::unbond(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + Pools::::fully_unbond(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); // Sanity check that unbond worked assert_eq!( @@ -423,7 +430,7 @@ frame_benchmarking::benchmarks! { // up when unbonding. let reward_account = Pools::::create_reward_account(1); assert!(frame_system::Account::::contains_key(&reward_account)); - Pools::::unbond(Origin::Signed(depositor.clone()).into(), depositor.clone()).unwrap(); + Pools::::fully_unbond(Origin::Signed(depositor.clone()).into(), depositor.clone()).unwrap(); // Sanity check that unbond worked assert_eq!( diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index b6a43471b8355..94b7f300099a4 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -155,6 +155,7 @@ impl pallet_nomination_pools::Config for Runtime { type StakingInterface = Staking; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = ConstU32<8>; type PalletId = PoolsPalletId; } diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 5b2c206d89031..47655454c5fde 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -309,18 +309,18 @@ use codec::Codec; use frame_support::{ - ensure, + defensive, ensure, pallet_prelude::{MaxEncodedLen, *}, storage::bounded_btree_map::BoundedBTreeMap, traits::{ Currency, Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, ExistenceRequirement, Get, }, - DefaultNoBound, RuntimeDebugNoBound, + CloneNoBound, DefaultNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_core::U256; -use sp_runtime::traits::{AccountIdConversion, Bounded, Convert, Saturating, Zero}; +use sp_runtime::traits::{AccountIdConversion, Bounded, CheckedSub, Convert, Saturating, Zero}; use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, ops::Div, vec::Vec}; @@ -382,8 +382,8 @@ enum AccountType { } /// A delegator in a pool. -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] -#[cfg_attr(feature = "std", derive(Clone, PartialEq))] +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, CloneNoBound)] +#[cfg_attr(feature = "std", derive(PartialEqNoBound, DefaultNoBound))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct Delegator { @@ -397,8 +397,96 @@ pub struct Delegator { /// This value lines up with the [`RewardPool::total_earnings`] after a delegator claims a /// payout. pub reward_pool_total_earnings: BalanceOf, - /// The era this delegator started unbonding at. - pub unbonding_era: Option, + /// The eras in which this delegator is unbonding, mapped from era index to the number of + /// points scheduled to unbond in the given era. + pub unbonding_eras: BoundedBTreeMap, T::MaxUnbonding>, +} + +impl Delegator { + #[cfg(any(test, debug_assertions))] + fn total_points(&self) -> BalanceOf { + self.active_points().saturating_add(self.unbonding_points()) + } + + /// Active balance of the delegator. + /// + /// This is derived from the ratio of points in the pool to which the delegator belongs to. + /// Might return different values based on the pool state for the same delegator and points. + pub(crate) fn active_balance(&self) -> BalanceOf { + if let Some(pool) = BondedPool::::get(self.pool_id).defensive() { + pool.points_to_balance(self.points) + } else { + Zero::zero() + } + } + + /// Active points of the delegator. + pub(crate) fn active_points(&self) -> BalanceOf { + self.points + } + + /// Inactive points of the delegator, waiting to be withdrawn. + pub(crate) fn unbonding_points(&self) -> BalanceOf { + self.unbonding_eras + .as_ref() + .iter() + .fold(BalanceOf::::zero(), |acc, (_, v)| acc.saturating_add(*v)) + } + + /// Try and unbond `points` from self, with the given target unbonding era. + /// + /// Returns `Ok(())` and updates `unbonding_eras` and `points` if success, `Err(_)` otherwise. + fn try_unbond( + &mut self, + points: BalanceOf, + unbonding_era: EraIndex, + ) -> Result<(), Error> { + if let Some(new_points) = self.points.checked_sub(&points) { + match self.unbonding_eras.get_mut(&unbonding_era) { + Some(already_unbonding_points) => + *already_unbonding_points = already_unbonding_points.saturating_add(points), + None => self + .unbonding_eras + .try_insert(unbonding_era, points) + .map(|old| { + if old.is_some() { + defensive!("value checked to not exist in the map; qed"); + } + }) + .map_err(|_| Error::::MaxUnbondingLimit)?, + } + self.points = new_points; + Ok(()) + } else { + Err(Error::::NotEnoughPointsToUnbond) + } + } + + /// Withdraw any funds in [`Self::unbonding_eras`] who's deadline in reached and is fully + /// unlocked. + /// + /// Returns a a subset of [`Self::unbonding_eras`] that got withdrawn. + /// + /// Infallible, noop if no unbonding eras exist. + fn withdraw_unlocked( + &mut self, + current_era: EraIndex, + ) -> BoundedBTreeMap, T::MaxUnbonding> { + // NOTE: if only drain-filter was stable.. + let mut removed_points = + BoundedBTreeMap::, T::MaxUnbonding>::default(); + self.unbonding_eras.retain(|e, p| { + if *e > current_era { + true + } else { + removed_points + .try_insert(*e, p.clone()) + .expect("source map is bounded, this is a subset, will be bounded; qed"); + false + } + }); + removed_points + } } /// A pool's possible states. @@ -510,28 +598,45 @@ impl BondedPool { BondedPools::::remove(self.id); } - /// Get the amount of points to issue for some new funds that will be bonded in the pool. - fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { + /// Convert the given amount of balance to points given the current pool state. + /// + /// This is often used for bonding and issuing new funds into the pool. + fn balance_to_point(&self, new_funds: BalanceOf) -> BalanceOf { let bonded_balance = T::StakingInterface::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); - Pallet::::points_to_issue(bonded_balance, self.points, new_funds) + Pallet::::balance_to_point(bonded_balance, self.points, new_funds) } - /// Get the amount of balance to unbond from the pool based on a delegator's points of the pool. - fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { + /// Convert the given number of points to balance given the current pool state. + /// + /// This is often used for unbonding. + fn points_to_balance(&self, points: BalanceOf) -> BalanceOf { let bonded_balance = T::StakingInterface::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); - Pallet::::balance_to_unbond(bonded_balance, self.points, delegator_points) + Pallet::::point_to_balance(bonded_balance, self.points, points) } /// Issue points to [`Self`] for `new_funds`. fn issue(&mut self, new_funds: BalanceOf) -> BalanceOf { - let points_to_issue = self.points_to_issue(new_funds); + let points_to_issue = self.balance_to_point(new_funds); self.points = self.points.saturating_add(points_to_issue); - points_to_issue } + /// Dissolve some points from the pool i.e. unbond the given amount of points from this pool. + /// This is the opposite of issuing some funds into the pool. + /// + /// Mutates self in place, but does not write anything to storage. + /// + /// Returns the equivalent balance amount that actually needs to get unbonded. + fn dissolve(&mut self, points: BalanceOf) -> BalanceOf { + // NOTE: do not optimize by removing `balance`. it must be computed before mutating + // `self.point`. + let balance = self.points_to_balance(points); + self.points = self.points.saturating_sub(points); + balance + } + /// Increment the delegator counter. Ensures that the pool and system delegator limits are /// respected. fn try_inc_delegators(&mut self) -> Result<(), DispatchError> { @@ -582,6 +687,12 @@ impl BondedPool { matches!(self.state, PoolState::Destroying) } + fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf) -> bool { + // NOTE: if we add `&& self.delegator_counter == 1`, then this becomes even more strict and + // ensures that there are no unbonding delegators hanging around either. + self.is_destroying() && self.points == alleged_depositor_points + } + /// Whether or not the pool is ok to be in `PoolSate::Open`. If this returns an `Err`, then the /// pool is unrecoverable and should be in the destroying state. fn ok_to_be_open(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { @@ -624,6 +735,7 @@ impl BondedPool { caller: &T::AccountId, target_account: &T::AccountId, target_delegator: &Delegator, + unbonding_points: BalanceOf, ) -> Result<(), DispatchError> { let is_permissioned = caller == target_account; let is_depositor = *target_account == self.roles.depositor; @@ -638,13 +750,26 @@ impl BondedPool { }, // Any delegator who is not the depositor can always unbond themselves (true, false) => (), - // The depositor can only start unbonding if the pool is already being destroyed and - // they are the delegator in the pool. Note that an invariant is once the pool is - // destroying it cannot switch states, so by being in destroying we are guaranteed no - // other delegators can possibly join. (_, true) => { - ensure!(target_delegator.points == self.points, Error::::NotOnlyDelegator); - ensure!(self.is_destroying(), Error::::NotDestroying); + if self.is_destroying_and_only_depositor(target_delegator.active_points()) { + // if the pool is about to be destroyed, anyone can unbond the depositor, and + // they can fully unbond. + } else { + // only the depositor can partially unbond, and they can only unbond up to the + // threshold. + ensure!(is_permissioned, Error::::DoesNotHavePermission); + let balance_after_unbond = { + let new_depositor_points = + target_delegator.active_points().saturating_sub(unbonding_points); + let mut depositor_after_unbond = (*target_delegator).clone(); + depositor_after_unbond.points = new_depositor_points; + depositor_after_unbond.active_balance() + }; + ensure!( + balance_after_unbond >= MinCreateBond::::get(), + Error::::NotOnlyDelegator + ); + } }, }; Ok(()) @@ -652,44 +777,21 @@ impl BondedPool { /// # Returns /// - /// * Ok(true) if [`Call::withdraw_unbonded`] can be called and the target account is the - /// depositor. - /// * Ok(false) if [`Call::withdraw_unbonded`] can be called and target account is *not* the - /// depositor. - /// * Err(DispatchError) if [`Call::withdraw_unbonded`] *cannot* be called. + /// * Ok(()) if [`Call::withdraw_unbonded`] can be called, `Err(DispatchError)` otherwise. fn ok_to_withdraw_unbonded_with( &self, caller: &T::AccountId, target_account: &T::AccountId, target_delegator: &Delegator, sub_pools: &SubPools, - ) -> Result { + ) -> Result<(), DispatchError> { if *target_account == self.roles.depositor { - // This is a depositor - if !sub_pools.no_era.points.is_zero() { - // Unbonded pool has some points, so if they are the last delegator they must be - // here. Since the depositor is the last to unbond, this should never be possible. - ensure!(sub_pools.with_era.len().is_zero(), Error::::NotOnlyDelegator); - ensure!( - sub_pools.no_era.points == target_delegator.points, - Error::::NotOnlyDelegator - ); - } else { - // No points in the `no_era` pool, so they must be in a `with_era` pool - // If there are no other delegators, this can be the only `with_era` pool since the - // depositor was the last to withdraw. This assumes with_era sub pools are destroyed - // whenever their points go to zero. - ensure!(sub_pools.with_era.len() == 1, Error::::NotOnlyDelegator); - sub_pools - .with_era - .values() - .next() - .filter(|only_unbonding_pool| { - only_unbonding_pool.points == target_delegator.points - }) - .ok_or(Error::::NotOnlyDelegator)?; - } - Ok(true) + ensure!( + sub_pools.sum_unbonding_points() == target_delegator.unbonding_points(), + Error::::NotOnlyDelegator + ); + debug_assert_eq!(self.delegator_counter, 1, "only delegator must exist at this point"); + Ok(()) } else { // This isn't a depositor let is_permissioned = caller == target_account; @@ -697,7 +799,7 @@ impl BondedPool { is_permissioned || self.can_kick(caller) || self.is_destroying(), Error::::NotKickerOrDestroying ); - Ok(false) + Ok(()) } } @@ -814,29 +916,46 @@ impl RewardPool { } } +/// An unbonding pool. This is always mapped with an era. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound, RuntimeDebugNoBound)] -#[cfg_attr(feature = "std", derive(Clone, PartialEq))] +#[cfg_attr(feature = "std", derive(Clone, PartialEq, Eq))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct UnbondPool { + /// The points in this pool. points: BalanceOf, + /// The funds in the pool. balance: BalanceOf, } impl UnbondPool { - fn points_to_issue(&self, new_funds: BalanceOf) -> BalanceOf { - Pallet::::points_to_issue(self.balance, self.points, new_funds) + fn balance_to_point(&self, new_funds: BalanceOf) -> BalanceOf { + Pallet::::balance_to_point(self.balance, self.points, new_funds) } - fn balance_to_unbond(&self, delegator_points: BalanceOf) -> BalanceOf { - Pallet::::balance_to_unbond(self.balance, self.points, delegator_points) + fn point_to_balance(&self, points: BalanceOf) -> BalanceOf { + Pallet::::point_to_balance(self.balance, self.points, points) } /// Issue points and update the balance given `new_balance`. fn issue(&mut self, new_funds: BalanceOf) { - self.points = self.points.saturating_add(self.points_to_issue(new_funds)); + self.points = self.points.saturating_add(self.balance_to_point(new_funds)); self.balance = self.balance.saturating_add(new_funds); } + + /// Dissolve some points from the unbonding pool, reducing the balance of the pool + /// proportionally. + /// + /// This is the opposite of `issue`. + /// + /// Returns the actual amount of `Balance` that was removed from the pool. + fn dissolve(&mut self, points: BalanceOf) -> BalanceOf { + let balance_to_unbond = self.point_to_balance(points); + self.points = self.points.saturating_sub(points); + self.balance = self.balance.saturating_sub(balance_to_unbond); + + balance_to_unbond + } } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound, RuntimeDebugNoBound)] @@ -878,6 +997,25 @@ impl SubPools { self } + + /// The sum of all unbonding points, regardless of whether they are actually unlocked or not. + fn sum_unbonding_points(&self) -> BalanceOf { + self.no_era.points.saturating_add( + self.with_era + .values() + .fold(BalanceOf::::zero(), |acc, pool| acc.saturating_add(pool.points)), + ) + } + + /// The sum of all unbonding balance, regardless of whether they are actually unlocked or not. + #[cfg(any(test, debug_assertions))] + fn sum_unbonding_balance(&self) -> BalanceOf { + self.no_era.balance.saturating_add( + self.with_era + .values() + .fold(BalanceOf::::zero(), |acc, pool| acc.saturating_add(pool.balance)), + ) + } } /// The maximum amount of eras an unbonding pool can exist prior to being merged with the @@ -939,6 +1077,9 @@ pub mod pallet { /// The maximum length, in bytes, that a pools metadata maybe. type MaxMetadataLen: Get; + + /// The maximum number of simultaneous unbonding chunks that can exist per delegator. + type MaxUnbonding: Get; } /// Minimum amount to bond to join a pool. @@ -946,6 +1087,9 @@ pub mod pallet { pub type MinJoinBond = StorageValue<_, BalanceOf, ValueQuery>; /// Minimum bond required to create a pool. + /// + /// This is the amount that the depositor must put as their initial stake in the pool, as an + /// indication of "skin in the game". #[pallet::storage] pub type MinCreateBond = StorageValue<_, BalanceOf, ValueQuery>; @@ -1071,12 +1215,15 @@ pub mod pallet { AccountBelongsToOtherPool, /// The pool has insufficient balance to bond as a nominator. InsufficientBond, - /// The delegator is already unbonding. + /// The delegator is already unbonding in this era. AlreadyUnbonding, - /// The delegator is not unbonding and thus cannot withdraw funds. - NotUnbonding, - /// Unbonded funds cannot be withdrawn yet because the bonding duration has not passed. - NotUnbondedYet, + /// The delegator is fully unbonded (and thus cannot access the bonded and reward pool + /// anymore to, for example, collect rewards). + FullyUnbonding, + /// The delegator cannot unbond further chunks due to reaching the limit. + MaxUnbondingLimit, + /// None of the funds can be withdrawn yet because the bonding duration has not passed. + CannotWithdrawAny, /// The amount does not meet the minimum bond to either join or create a pool. MinimumBondNotMet, /// The transaction could not be executed due to overflow risk for the pool. @@ -1106,6 +1253,8 @@ pub mod pallet { /// Some error occurred that should never happen. This should be reported to the /// maintainers. DefensiveError, + /// Not enough points. Ty unbonding less. + NotEnoughPointsToUnbond, } #[pallet::call] @@ -1156,16 +1305,17 @@ pub mod pallet { // next 2 eras because their vote weight will not be counted until the // snapshot in active era + 1. reward_pool_total_earnings: reward_pool.total_earnings, - unbonding_era: None, + unbonding_eras: Default::default(), }, ); - bonded_pool.put(); + Self::deposit_event(Event::::Bonded { delegator: who, pool_id, bonded: amount, joined: true, }); + bonded_pool.put(); Ok(()) } @@ -1234,12 +1384,14 @@ pub mod pallet { Ok(()) } - /// Unbond _all_ of the `delegator_account`'s funds from the pool. + /// Unbond up to `unbonding_points` of the `delegator_account`'s funds from the pool. It + /// implicitly collects the rewards one last time, since not doing so would mean some + /// rewards would go forfeited. /// /// Under certain conditions, this call can be dispatched permissionlessly (i.e. by any /// account). /// - /// # Conditions for a permissionless dispatch + /// # Conditions for a permissionless dispatch. /// /// * The pool is blocked and the caller is either the root or state-toggler. This is /// refereed to as a kick. @@ -1247,7 +1399,7 @@ pub mod pallet { /// * The pool is destroying, the delegator is the depositor and no other delegators are in /// the pool. /// - /// # Conditions for permissioned dispatch (i.e. the caller is also the + /// ## Conditions for permissioned dispatch (i.e. the caller is also the /// `delegator_account`): /// /// * The caller is not the depositor. @@ -1262,12 +1414,21 @@ pub mod pallet { /// `NoMoreChunks` error from the staking system. #[pallet::weight(T::WeightInfo::unbond())] #[transactional] - pub fn unbond(origin: OriginFor, delegator_account: T::AccountId) -> DispatchResult { + pub fn unbond( + origin: OriginFor, + delegator_account: T::AccountId, + #[pallet::compact] unbonding_points: BalanceOf, + ) -> DispatchResult { let caller = ensure_signed(origin)?; let (mut delegator, mut bonded_pool, mut reward_pool) = Self::get_delegator_with_pools(&delegator_account)?; - bonded_pool.ok_to_unbond_with(&caller, &delegator_account, &delegator)?; + bonded_pool.ok_to_unbond_with( + &caller, + &delegator_account, + &delegator, + unbonding_points, + )?; // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the bonded pool and thus they can no longer claim their payouts. @@ -1279,17 +1440,16 @@ pub mod pallet { &mut reward_pool, )?; - let balance_to_unbond = bonded_pool.balance_to_unbond(delegator.points); + let current_era = T::StakingInterface::current_era(); + let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); - // Update the bonded pool. Note that we must do this *after* calculating the balance - // to unbond so we have the correct points for the balance:share ratio. - bonded_pool.points = bonded_pool.points.saturating_sub(delegator.points); + // Try and unbond in the delegator map. + delegator.try_unbond(unbonding_points, unbond_era)?; - // Unbond in the actual underlying pool - T::StakingInterface::unbond(bonded_pool.bonded_account(), balance_to_unbond)?; + // Unbond in the actual underlying nominator. + let unbonding_balance = bonded_pool.dissolve(unbonding_points); + T::StakingInterface::unbond(bonded_pool.bonded_account(), unbonding_balance)?; - let current_era = T::StakingInterface::current_era(); - let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); // Note that we lazily create the unbonding pools here if they don't already exist let mut sub_pools = SubPoolsStorage::::get(delegator.pool_id) .unwrap_or_default() @@ -1305,19 +1465,18 @@ pub mod pallet { // always enough space to insert. .defensive_map_err(|_| Error::::DefensiveError)?; } + sub_pools .with_era .get_mut(&unbond_era) // The above check ensures the pool exists. .defensive_ok_or_else(|| Error::::DefensiveError)? - .issue(balance_to_unbond); - - delegator.unbonding_era = Some(unbond_era); + .issue(unbonding_balance); Self::deposit_event(Event::::Unbonded { delegator: delegator_account.clone(), pool_id: delegator.pool_id, - amount: balance_to_unbond, + amount: unbonding_balance, }); // Now that we know everything has worked write the items to storage. @@ -1349,8 +1508,11 @@ pub mod pallet { Ok(()) } - /// Withdraw unbonded funds for the `target` delegator. Under certain conditions, - /// this call can be dispatched permissionlessly (i.e. by any account). + /// Withdraw unbonded funds from `delegator_account`. If no bonded funds can be unbonded, an + /// error is returned. + /// + /// Under certain conditions, this call can be dispatched permissionlessly (i.e. by any + /// account). /// /// # Conditions for a permissionless dispatch /// @@ -1375,59 +1537,55 @@ pub mod pallet { num_slashing_spans: u32, ) -> DispatchResultWithPostInfo { let caller = ensure_signed(origin)?; - let delegator = + let mut delegator = Delegators::::get(&delegator_account).ok_or(Error::::DelegatorNotFound)?; - let unbonding_era = delegator.unbonding_era.ok_or(Error::::NotUnbonding)?; let current_era = T::StakingInterface::current_era(); - ensure!(current_era >= unbonding_era, Error::::NotUnbondedYet); - let mut sub_pools = SubPoolsStorage::::get(delegator.pool_id) - .defensive_ok_or_else(|| Error::::SubPoolsNotFound)?; let bonded_pool = BondedPool::::get(delegator.pool_id) .defensive_ok_or_else(|| Error::::PoolNotFound)?; - let should_remove_pool = bonded_pool.ok_to_withdraw_unbonded_with( + let mut sub_pools = SubPoolsStorage::::get(delegator.pool_id) + .defensive_ok_or_else(|| Error::::SubPoolsNotFound)?; + + bonded_pool.ok_to_withdraw_unbonded_with( &caller, &delegator_account, &delegator, &sub_pools, )?; + // NOTE: must do this after we have done the `ok_to_withdraw_unbonded_other_with` check. + let withdrawn_points = delegator.withdraw_unlocked(current_era); + ensure!(!withdrawn_points.is_empty(), Error::::CannotWithdrawAny); + // Before calculate the `balance_to_unbond`, with call withdraw unbonded to ensure the - // `non_locked_balance` is correct. + // `transferrable_balance` is correct. T::StakingInterface::withdraw_unbonded( bonded_pool.bonded_account(), num_slashing_spans, )?; - let balance_to_unbond = - if let Some(pool) = sub_pools.with_era.get_mut(&unbonding_era) { - let balance_to_unbond = pool.balance_to_unbond(delegator.points); - pool.points = pool.points.saturating_sub(delegator.points); - pool.balance = pool.balance.saturating_sub(balance_to_unbond); - if pool.points.is_zero() { - // Clean up pool that is no longer used - sub_pools.with_era.remove(&unbonding_era); + let balance_to_unbond = withdrawn_points + .iter() + .fold(BalanceOf::::zero(), |accumulator, (era, unlocked_points)| { + if let Some(era_pool) = sub_pools.with_era.get_mut(&era) { + let balance_to_unbond = era_pool.dissolve(*unlocked_points); + if era_pool.points.is_zero() { + sub_pools.with_era.remove(&era); + } + accumulator.saturating_add(balance_to_unbond) + } else { + // A pool does not belong to this era, so it must have been merged to the + // era-less pool. + accumulator.saturating_add(sub_pools.no_era.dissolve(*unlocked_points)) } - - balance_to_unbond - } else { - // A pool does not belong to this era, so it must have been merged to the - // era-less pool. - let balance_to_unbond = sub_pools.no_era.balance_to_unbond(delegator.points); - sub_pools.no_era.points = - sub_pools.no_era.points.saturating_sub(delegator.points); - sub_pools.no_era.balance = - sub_pools.no_era.balance.saturating_sub(balance_to_unbond); - - balance_to_unbond - } + }) // A call to this function may cause the pool's stash to get dusted. If this happens // before the last delegator has withdrawn, then all subsequent withdraws will be 0. // However the unbond pools do no get updated to reflect this. In the aforementioned // scenario, this check ensures we don't try to withdraw funds that don't exist. // This check is also defensive in cases where the unbond pool does not update its - // balance (e.g. a bug in the slashing hook.) We gracefully proceed in - // order to ensure delegators can leave the pool and it can be destroyed. + // balance (e.g. a bug in the slashing hook.) We gracefully proceed in order to + // ensure delegators can leave the pool and it can be destroyed. .min(bonded_pool.transferrable_balance()); T::Currency::transfer( @@ -1436,44 +1594,32 @@ pub mod pallet { balance_to_unbond, ExistenceRequirement::AllowDeath, ) - .defensive_map_err(|e| e)?; + .defensive()?; + Self::deposit_event(Event::::Withdrawn { delegator: delegator_account.clone(), pool_id: delegator.pool_id, amount: balance_to_unbond, }); - let post_info_weight = if should_remove_pool { - let reward_account = bonded_pool.reward_account(); - ReversePoolIdLookup::::remove(bonded_pool.bonded_account()); - RewardPools::::remove(delegator.pool_id); - Self::deposit_event(Event::::Destroyed { pool_id: delegator.pool_id }); - SubPoolsStorage::::remove(delegator.pool_id); - // Kill accounts from storage by making their balance go below ED. We assume that - // the accounts have no references that would prevent destruction once we get to - // this point. - debug_assert_eq!( - T::StakingInterface::total_stake(&bonded_pool.bonded_account()) - .unwrap_or_default(), - Zero::zero() - ); - let reward_pool_remaining = T::Currency::free_balance(&reward_account); - // This shouldn't fail, but if it does we don't really care - let _ = T::Currency::transfer( - &reward_account, - &delegator_account, - reward_pool_remaining, - ExistenceRequirement::AllowDeath, - ); - T::Currency::make_free_balance_be(&bonded_pool.bonded_account(), Zero::zero()); - bonded_pool.remove(); - None + let post_info_weight = if delegator.active_points().is_zero() { + // delegator being reaped. + Delegators::::remove(&delegator_account); + + if delegator_account == bonded_pool.roles.depositor { + Pallet::::dissolve_pool(bonded_pool); + None + } else { + bonded_pool.dec_delegators().put(); + SubPoolsStorage::::insert(&delegator.pool_id, sub_pools); + Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) + } } else { - bonded_pool.dec_delegators().put(); + // we certainly don't need to delete any pools, because no one is being removed. SubPoolsStorage::::insert(&delegator.pool_id, sub_pools); + Delegators::::insert(&delegator_account, delegator); Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) }; - Delegators::::remove(&delegator_account); Ok(post_info_weight.into()) } @@ -1545,7 +1691,7 @@ pub mod pallet { pool_id, points, reward_pool_total_earnings: Zero::zero(), - unbonding_era: None, + unbonding_eras: Default::default(), }, ); RewardPools::::insert( @@ -1693,6 +1839,49 @@ pub mod pallet { } impl Pallet { + /// Remove everything related to the given bonded pool. + /// + /// All sub-pools are also deleted. All accounts are dusted and the leftover of the reward + /// account is returned to the depositor. + pub fn dissolve_pool(bonded_pool: BondedPool) { + let reward_account = bonded_pool.reward_account(); + let bonded_account = bonded_pool.bonded_account(); + + ReversePoolIdLookup::::remove(&bonded_account); + RewardPools::::remove(bonded_pool.id); + SubPoolsStorage::::remove(bonded_pool.id); + + // Kill accounts from storage by making their balance go below ED. We assume that the + // accounts have no references that would prevent destruction once we get to this point. We + // don't work with the system pallet directly, but + // 1. we drain the reward account and kill it. This account should never have any extra + // consumers anyway. + // 2. the bonded account should become a 'killed stash' in the staking system, and all of + // its consumers removed. + debug_assert_eq!(frame_system::Pallet::::consumers(&reward_account), 0); + debug_assert_eq!(frame_system::Pallet::::consumers(&bonded_account), 0); + debug_assert_eq!( + T::StakingInterface::total_stake(&bonded_account).unwrap_or_default(), + Zero::zero() + ); + + // This shouldn't fail, but if it does we don't really care + let reward_pool_remaining = T::Currency::free_balance(&reward_account); + let _ = T::Currency::transfer( + &reward_account, + &bonded_pool.roles.depositor, + reward_pool_remaining, + ExistenceRequirement::AllowDeath, + ); + + // TODO: this is purely defensive. + T::Currency::make_free_balance_be(&reward_account, Zero::zero()); + T::Currency::make_free_balance_be(&bonded_pool.bonded_account(), Zero::zero()); + + Self::deposit_event(Event::::Destroyed { pool_id: bonded_pool.id }); + bonded_pool.remove(); + } + /// Create the main, bonded account of a pool with the given id. pub fn create_bonded_account(id: PoolId) -> T::AccountId { T::PalletId::get().into_sub_account((AccountType::Bonded, id)) @@ -1730,9 +1919,9 @@ impl Pallet { Delegators::::insert(delegator_account, delegator); } - /// Calculate the number of points to issue from a pool as `(current_points / current_balance) * - /// new_funds` except for some zero edge cases; see logic and tests for details. - fn points_to_issue( + /// Calculate the equivalent point of `new_funds` in a pool with `current_balance` and + /// `current_points`. + fn balance_to_point( current_balance: BalanceOf, current_points: BalanceOf, new_funds: BalanceOf, @@ -1758,22 +1947,22 @@ impl Pallet { } } - // Calculate the balance of a pool to unbond as `(current_balance / current_points) * - // delegator_points`. Returns zero if any of the inputs are zero. - fn balance_to_unbond( + /// Calculate the equivalent balance of `points` in a pool with `current_balance` and + /// `current_points`. + fn point_to_balance( current_balance: BalanceOf, current_points: BalanceOf, - delegator_points: BalanceOf, + points: BalanceOf, ) -> BalanceOf { let u256 = |x| T::BalanceToU256::convert(x); let balance = |x| T::U256ToBalance::convert(x); - if current_balance.is_zero() || current_points.is_zero() || delegator_points.is_zero() { + if current_balance.is_zero() || current_points.is_zero() || points.is_zero() { // There is nothing to unbond return Zero::zero() } - // Equivalent of (current_balance / current_points) * delegator_points - balance(u256(current_balance).saturating_mul(u256(delegator_points))) + // Equivalent of (current_balance / current_points) * points + balance(u256(current_balance).saturating_mul(u256(points))) // We check for zero above .div(current_points) } @@ -1782,15 +1971,12 @@ impl Pallet { /// /// Returns the payout amount. fn calculate_delegator_payout( + delegator: &mut Delegator, bonded_pool: &mut BondedPool, reward_pool: &mut RewardPool, - delegator: &mut Delegator, ) -> Result, DispatchError> { let u256 = |x| T::BalanceToU256::convert(x); let balance = |x| T::U256ToBalance::convert(x); - // If the delegator is unbonding they cannot claim rewards. Note that when the delegator - // goes to unbond, the unbond function should claim rewards for the final time. - ensure!(delegator.unbonding_era.is_none(), Error::::AlreadyUnbonding); let last_total_earnings = reward_pool.total_earnings; reward_pool.update_total_earnings_and_balance(bonded_pool.id); @@ -1816,7 +2002,7 @@ impl Pallet { // The points of the reward pool that belong to the delegator. let delegator_virtual_points = // The delegators portion of the reward pool - u256(delegator.points) + u256(delegator.active_points()) // times the amount the pool has earned since the delegator last claimed. .saturating_mul(u256(new_earnings_since_last_claim)); @@ -1859,10 +2045,12 @@ impl Pallet { reward_pool: &mut RewardPool, ) -> Result, DispatchError> { debug_assert_eq!(delegator.pool_id, bonded_pool.id); + // a delegator who has no skin in the game anymore cannot claim any rewards. + ensure!(!delegator.active_points().is_zero(), Error::::FullyUnbonding); let was_destroying = bonded_pool.is_destroying(); let delegator_payout = - Self::calculate_delegator_payout(bonded_pool, reward_pool, delegator)?; + Self::calculate_delegator_payout(delegator, bonded_pool, reward_pool)?; // Transfer payout to the delegator. T::Currency::transfer( @@ -1948,17 +2136,27 @@ impl Pallet { let mut all_delegators = 0u32; Delegators::::iter().for_each(|(_, d)| { assert!(BondedPools::::contains_key(d.pool_id)); + assert!(!d.total_points().is_zero(), "no delegator should have zero points: {:?}", d); *pools_delegators.entry(d.pool_id).or_default() += 1; all_delegators += 1; }); - BondedPools::::iter().for_each(|(id, bonded_pool)| { + BondedPools::::iter().for_each(|(id, inner)| { + let bonded_pool = BondedPool { id, inner }; assert_eq!( pools_delegators.get(&id).map(|x| *x).unwrap_or_default(), bonded_pool.delegator_counter ); assert!(MaxDelegatorsPerPool::::get() .map_or(true, |max| bonded_pool.delegator_counter <= max)); + + let depositor = Delegators::::get(&bonded_pool.roles.depositor).unwrap(); + assert!( + bonded_pool.is_destroying_and_only_depositor(depositor.active_points()) || + depositor.active_points() >= MinCreateBond::::get(), + "depositor must always have MinCreateBond stake in the pool, except for when the \ + pool is being destroyed and the depositor is the last member", + ); }); assert!(MaxDelegators::::get().map_or(true, |max| all_delegators <= max)); @@ -1966,24 +2164,20 @@ impl Pallet { return Ok(()) } - for (pool_id, _) in BondedPools::::iter() { + for (pool_id, _pool) in BondedPools::::iter() { let pool_account = Pallet::::create_bonded_account(pool_id); let subs = SubPoolsStorage::::get(pool_id).unwrap_or_default(); - let sum_unbonding_balance = subs - .with_era - .into_iter() - .map(|(_, v)| v) - .chain(sp_std::iter::once(subs.no_era)) - .map(|unbond_pool| unbond_pool.balance) - .fold(Zero::zero(), |a, b| a + b); + let sum_unbonding_balance = subs.sum_unbonding_balance(); let bonded_balance = T::StakingInterface::active_stake(&pool_account).unwrap_or_default(); let total_balance = T::Currency::total_balance(&pool_account); assert!( total_balance >= bonded_balance + sum_unbonding_balance, - "total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}", + "faulty pool: {:?} / {:?}, total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}", + pool_id, + _pool, total_balance, bonded_balance, sum_unbonding_balance @@ -1992,6 +2186,20 @@ impl Pallet { Ok(()) } + + /// Fully unbond the shares of `delegator`, when executed from `origin`. + /// + /// This is useful for backwards compatibility with the majority of tests that only deal with + /// full unbonding, not partial unbonding. + #[cfg(any(feature = "runtime-benchmarks", test))] + pub fn fully_unbond( + origin: frame_system::pallet_prelude::OriginFor, + delegator: T::AccountId, + ) -> DispatchResult { + let points = + Delegators::::get(&delegator).map(|d| d.active_points()).unwrap_or_default(); + Self::unbond(origin, delegator, points) + } } impl OnStakerSlash> for Pallet { diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 28b00c3488678..a84c45398ff72 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -2,6 +2,7 @@ use super::*; use crate::{self as pools}; use frame_support::{assert_ok, parameter_types, PalletId}; use frame_system::RawOrigin; +use std::collections::HashMap; pub type AccountId = u128; pub type Balance = u128; @@ -18,9 +19,11 @@ pub fn default_reward_account() -> AccountId { parameter_types! { pub static CurrentEra: EraIndex = 0; - static BondedBalanceMap: std::collections::HashMap = Default::default(); - static UnbondingBalanceMap: std::collections::HashMap = Default::default(); pub static BondingDuration: EraIndex = 3; + static BondedBalanceMap: HashMap = Default::default(); + static UnbondingBalanceMap: HashMap = Default::default(); + #[derive(Clone, PartialEq)] + pub static MaxUnbonding: u32 = 8; pub static Nominations: Vec = vec![]; } @@ -170,6 +173,7 @@ impl pools::Config for Runtime { type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; type PalletId = PoolsPalletId; type MaxMetadataLen = MaxMetadataLen; + type MaxUnbonding = MaxUnbonding; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -198,6 +202,11 @@ impl ExtBuilder { self } + pub(crate) fn ed(self, ed: Balance) -> Self { + ExistentialDeposit::set(ed); + self + } + pub(crate) fn with_check(self, level: u8) -> Self { CheckLevel::set(level); self @@ -269,6 +278,14 @@ pub(crate) fn pool_events_since_last_call() -> Vec> { events.into_iter().skip(already_seen).collect() } +/// Same as `fully_unbond`, in permissioned setting. +pub fn fully_unbond_permissioned(delegator: AccountId) -> DispatchResult { + let points = Delegators::::get(&delegator) + .map(|d| d.active_points()) + .unwrap_or_default(); + Pools::unbond(Origin::signed(delegator), delegator, points) +} + #[cfg(test)] mod test { use super::*; diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 134d344b99bc9..15beb0572b9e1 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -16,9 +16,9 @@ // limitations under the License. use super::*; -use crate::mock::*; +use crate::{mock::*, Event}; use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, + assert_noop, assert_ok, assert_storage_noop, bounded_btree_map, storage::{with_transaction, TransactionOutcome}, }; @@ -30,6 +30,13 @@ macro_rules! unbonding_pools_with_era { }}; } +macro_rules! delegator_unbonding_eras { + ($( $any:tt )*) => {{ + let x: BoundedBTreeMap = bounded_btree_map!($( $any )*); + x + }}; +} + pub const DEFAULT_ROLES: PoolRoles = PoolRoles { depositor: 10, root: 900, nominator: 901, state_toggler: 902 }; @@ -61,12 +68,7 @@ fn test_setup_works() { ); assert_eq!( Delegators::::get(10).unwrap(), - Delegator:: { - pool_id: last_pool, - points: 10, - reward_pool_total_earnings: 0, - unbonding_era: None - } + Delegator:: { pool_id: last_pool, points: 10, ..Default::default() } ) }) } @@ -87,41 +89,41 @@ mod bonded_pool { // 1 points : 1 balance ratio StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); - assert_eq!(bonded_pool.points_to_issue(10), 10); - assert_eq!(bonded_pool.points_to_issue(0), 0); + assert_eq!(bonded_pool.balance_to_point(10), 10); + assert_eq!(bonded_pool.balance_to_point(0), 0); // 2 points : 1 balance ratio StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 50); - assert_eq!(bonded_pool.points_to_issue(10), 20); + assert_eq!(bonded_pool.balance_to_point(10), 20); // 1 points : 2 balance ratio StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 50; - assert_eq!(bonded_pool.points_to_issue(10), 5); + assert_eq!(bonded_pool.balance_to_point(10), 5); // 100 points : 0 balance ratio StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); bonded_pool.points = 100; - assert_eq!(bonded_pool.points_to_issue(10), 100 * 10); + assert_eq!(bonded_pool.balance_to_point(10), 100 * 10); // 0 points : 100 balance StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 100; - assert_eq!(bonded_pool.points_to_issue(10), 10); + assert_eq!(bonded_pool.balance_to_point(10), 10); // 10 points : 3 balance ratio StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 30); - assert_eq!(bonded_pool.points_to_issue(10), 33); + assert_eq!(bonded_pool.balance_to_point(10), 33); // 2 points : 3 balance ratio StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 300); bonded_pool.points = 200; - assert_eq!(bonded_pool.points_to_issue(10), 6); + assert_eq!(bonded_pool.balance_to_point(10), 6); // 4 points : 9 balance ratio StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 900); bonded_pool.points = 400; - assert_eq!(bonded_pool.points_to_issue(90), 40); + assert_eq!(bonded_pool.balance_to_point(90), 40); } #[test] @@ -138,37 +140,37 @@ mod bonded_pool { }; StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); - assert_eq!(bonded_pool.balance_to_unbond(10), 10); - assert_eq!(bonded_pool.balance_to_unbond(0), 0); + assert_eq!(bonded_pool.points_to_balance(10), 10); + assert_eq!(bonded_pool.points_to_balance(0), 0); // 2 balance : 1 points ratio bonded_pool.points = 50; - assert_eq!(bonded_pool.balance_to_unbond(10), 20); + assert_eq!(bonded_pool.points_to_balance(10), 20); // 100 balance : 0 points ratio StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); bonded_pool.points = 0; - assert_eq!(bonded_pool.balance_to_unbond(10), 0); + assert_eq!(bonded_pool.points_to_balance(10), 0); // 0 balance : 100 points ratio StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); bonded_pool.points = 100; - assert_eq!(bonded_pool.balance_to_unbond(10), 0); + assert_eq!(bonded_pool.points_to_balance(10), 0); // 10 balance : 3 points ratio StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 30; - assert_eq!(bonded_pool.balance_to_unbond(10), 33); + assert_eq!(bonded_pool.points_to_balance(10), 33); // 2 balance : 3 points ratio StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 200); bonded_pool.points = 300; - assert_eq!(bonded_pool.balance_to_unbond(10), 6); + assert_eq!(bonded_pool.points_to_balance(10), 6); // 4 balance : 9 points ratio StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 400); bonded_pool.points = 900; - assert_eq!(bonded_pool.balance_to_unbond(90), 40); + assert_eq!(bonded_pool.points_to_balance(90), 40); } #[test] @@ -242,72 +244,72 @@ mod unbond_pool { fn points_to_issue_works() { // 1 points : 1 balance ratio let unbond_pool = UnbondPool:: { points: 100, balance: 100 }; - assert_eq!(unbond_pool.points_to_issue(10), 10); - assert_eq!(unbond_pool.points_to_issue(0), 0); + assert_eq!(unbond_pool.balance_to_point(10), 10); + assert_eq!(unbond_pool.balance_to_point(0), 0); // 2 points : 1 balance ratio let unbond_pool = UnbondPool:: { points: 100, balance: 50 }; - assert_eq!(unbond_pool.points_to_issue(10), 20); + assert_eq!(unbond_pool.balance_to_point(10), 20); // 1 points : 2 balance ratio let unbond_pool = UnbondPool:: { points: 50, balance: 100 }; - assert_eq!(unbond_pool.points_to_issue(10), 5); + assert_eq!(unbond_pool.balance_to_point(10), 5); // 100 points : 0 balance ratio let unbond_pool = UnbondPool:: { points: 100, balance: 0 }; - assert_eq!(unbond_pool.points_to_issue(10), 100 * 10); + assert_eq!(unbond_pool.balance_to_point(10), 100 * 10); // 0 points : 100 balance let unbond_pool = UnbondPool:: { points: 0, balance: 100 }; - assert_eq!(unbond_pool.points_to_issue(10), 10); + assert_eq!(unbond_pool.balance_to_point(10), 10); // 10 points : 3 balance ratio let unbond_pool = UnbondPool:: { points: 100, balance: 30 }; - assert_eq!(unbond_pool.points_to_issue(10), 33); + assert_eq!(unbond_pool.balance_to_point(10), 33); // 2 points : 3 balance ratio let unbond_pool = UnbondPool:: { points: 200, balance: 300 }; - assert_eq!(unbond_pool.points_to_issue(10), 6); + assert_eq!(unbond_pool.balance_to_point(10), 6); // 4 points : 9 balance ratio let unbond_pool = UnbondPool:: { points: 400, balance: 900 }; - assert_eq!(unbond_pool.points_to_issue(90), 40); + assert_eq!(unbond_pool.balance_to_point(90), 40); } #[test] fn balance_to_unbond_works() { // 1 balance : 1 points ratio let unbond_pool = UnbondPool:: { points: 100, balance: 100 }; - assert_eq!(unbond_pool.balance_to_unbond(10), 10); - assert_eq!(unbond_pool.balance_to_unbond(0), 0); + assert_eq!(unbond_pool.point_to_balance(10), 10); + assert_eq!(unbond_pool.point_to_balance(0), 0); // 1 balance : 2 points ratio let unbond_pool = UnbondPool:: { points: 100, balance: 50 }; - assert_eq!(unbond_pool.balance_to_unbond(10), 5); + assert_eq!(unbond_pool.point_to_balance(10), 5); // 2 balance : 1 points ratio let unbond_pool = UnbondPool:: { points: 50, balance: 100 }; - assert_eq!(unbond_pool.balance_to_unbond(10), 20); + assert_eq!(unbond_pool.point_to_balance(10), 20); // 100 balance : 0 points ratio let unbond_pool = UnbondPool:: { points: 0, balance: 100 }; - assert_eq!(unbond_pool.balance_to_unbond(10), 0); + assert_eq!(unbond_pool.point_to_balance(10), 0); // 0 balance : 100 points ratio let unbond_pool = UnbondPool:: { points: 100, balance: 0 }; - assert_eq!(unbond_pool.balance_to_unbond(10), 0); + assert_eq!(unbond_pool.point_to_balance(10), 0); // 10 balance : 3 points ratio let unbond_pool = UnbondPool:: { points: 30, balance: 100 }; - assert_eq!(unbond_pool.balance_to_unbond(10), 33); + assert_eq!(unbond_pool.point_to_balance(10), 33); // 2 balance : 3 points ratio let unbond_pool = UnbondPool:: { points: 300, balance: 200 }; - assert_eq!(unbond_pool.balance_to_unbond(10), 6); + assert_eq!(unbond_pool.point_to_balance(10), 6); // 4 balance : 9 points ratio let unbond_pool = UnbondPool:: { points: 900, balance: 400 }; - assert_eq!(unbond_pool.balance_to_unbond(90), 40); + assert_eq!(unbond_pool.point_to_balance(90), 40); } } @@ -408,12 +410,7 @@ mod join { // then assert_eq!( Delegators::::get(&11).unwrap(), - Delegator:: { - pool_id: 1, - points: 2, - reward_pool_total_earnings: 0, - unbonding_era: None - } + Delegator:: { pool_id: 1, points: 2, ..Default::default() } ); assert_eq!(BondedPool::::get(1).unwrap(), bonded(12, 2)); @@ -431,12 +428,7 @@ mod join { // Then assert_eq!( Delegators::::get(&12).unwrap(), - Delegator:: { - pool_id: 1, - points: 24, - reward_pool_total_earnings: 0, - unbonding_era: None - } + Delegator:: { pool_id: 1, points: 24, ..Default::default() } ); assert_eq!(BondedPool::::get(1).unwrap(), bonded(12 + 24, 3)); }); @@ -570,7 +562,12 @@ mod claim_payout { use super::*; fn del(points: Balance, reward_pool_total_earnings: Balance) -> Delegator { - Delegator { pool_id: 1, points, reward_pool_total_earnings, unbonding_era: None } + Delegator { + pool_id: 1, + points, + reward_pool_total_earnings, + unbonding_eras: Default::default(), + } } fn rew(balance: Balance, points: u32, total_earnings: Balance) -> RewardPool { @@ -816,20 +813,18 @@ mod claim_payout { } #[test] - fn calculate_delegator_payout_errors_if_a_delegator_is_unbonding() { - ExtBuilder::default().build_and_execute(|| { + fn reward_payout_errors_if_a_delegator_is_fully_unbonding() { + ExtBuilder::default().add_delegators(vec![(11, 11)]).build_and_execute(|| { + // fully unbond the delegator. + assert_ok!(Pools::fully_unbond(Origin::signed(11), 11)); + let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); - let mut delegator = Delegators::::get(10).unwrap(); - delegator.unbonding_era = Some(0 + 3); + let mut delegator = Delegators::::get(11).unwrap(); assert_noop!( - Pools::calculate_delegator_payout( - &mut bonded_pool, - &mut reward_pool, - &mut delegator - ), - Error::::AlreadyUnbonding + Pools::do_reward_payout(&11, &mut delegator, &mut bonded_pool, &mut reward_pool,), + Error::::FullyUnbonding ); }); } @@ -847,9 +842,9 @@ mod claim_payout { // Given no rewards have been earned // When let payout = Pools::calculate_delegator_payout( + &mut delegator, &mut bonded_pool, &mut reward_pool, - &mut delegator, ) .unwrap(); @@ -863,9 +858,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut delegator, &mut bonded_pool, &mut reward_pool, - &mut delegator, ) .unwrap(); @@ -879,9 +874,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut delegator, &mut bonded_pool, &mut reward_pool, - &mut delegator, ) .unwrap(); @@ -895,9 +890,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut delegator, &mut bonded_pool, &mut reward_pool, - &mut delegator, ) .unwrap(); @@ -931,9 +926,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut del_10, &mut bonded_pool, &mut reward_pool, - &mut del_10, ) .unwrap(); @@ -946,9 +941,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut del_40, &mut bonded_pool, &mut reward_pool, - &mut del_40, ) .unwrap(); @@ -969,9 +964,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut del_50, &mut bonded_pool, &mut reward_pool, - &mut del_50, ) .unwrap(); @@ -987,9 +982,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut del_10, &mut bonded_pool, &mut reward_pool, - &mut del_10, ) .unwrap(); @@ -1002,9 +997,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut del_40, &mut bonded_pool, &mut reward_pool, - &mut del_40, ) .unwrap(); @@ -1020,9 +1015,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut del_50, &mut bonded_pool, &mut reward_pool, - &mut del_50, ) .unwrap(); @@ -1046,9 +1041,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut del_10, &mut bonded_pool, &mut reward_pool, - &mut del_10, ) .unwrap(); @@ -1065,9 +1060,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut del_10, &mut bonded_pool, &mut reward_pool, - &mut del_10, ) .unwrap(); @@ -1095,9 +1090,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut del_10, &mut bonded_pool, &mut reward_pool, - &mut del_10, ) .unwrap(); @@ -1110,9 +1105,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut del_40, &mut bonded_pool, &mut reward_pool, - &mut del_40, ) .unwrap(); @@ -1125,9 +1120,9 @@ mod claim_payout { // When let payout = Pools::calculate_delegator_payout( + &mut del_50, &mut bonded_pool, &mut reward_pool, - &mut del_50, ) .unwrap(); @@ -1377,7 +1372,7 @@ mod unbond { fn unbond_of_1_works() { ExtBuilder::default().build_and_execute(|| { unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond(Origin::signed(10), 10)); + assert_ok!(fully_unbond_permissioned(10)); assert_eq!( SubPoolsStorage::::get(1).unwrap().with_era, @@ -1413,7 +1408,7 @@ mod unbond { Balances::make_free_balance_be(&default_reward_account(), ed + 600); // When - assert_ok!(Pools::unbond(Origin::signed(40), 40)); + assert_ok!(fully_unbond_permissioned(40)); // Then assert_eq!( @@ -1434,12 +1429,15 @@ mod unbond { ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 94); - assert_eq!(Delegators::::get(40).unwrap().unbonding_era, Some(0 + 3)); + assert_eq!( + Delegators::::get(40).unwrap().unbonding_eras, + delegator_unbonding_eras!(0 + 3 => 40) + ); assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding // When unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond(Origin::signed(550), 550)); + assert_ok!(fully_unbond_permissioned(550)); // Then assert_eq!( @@ -1459,11 +1457,14 @@ mod unbond { } ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 2); - assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0 + 3)); + assert_eq!( + Delegators::::get(550).unwrap().unbonding_eras, + delegator_unbonding_eras!(0 + 3 => 550) + ); assert_eq!(Balances::free_balance(&550), 550 + 550); // When - assert_ok!(Pools::unbond(Origin::signed(10), 10)); + assert_ok!(fully_unbond_permissioned(10)); // Then assert_eq!( @@ -1483,7 +1484,10 @@ mod unbond { } ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); - assert_eq!(Delegators::::get(550).unwrap().unbonding_era, Some(0 + 3)); + assert_eq!( + Delegators::::get(550).unwrap().unbonding_eras, + delegator_unbonding_eras!(0 + 3 => 550) + ); assert_eq!(Balances::free_balance(&550), 550 + 550); }); } @@ -1510,7 +1514,7 @@ mod unbond { let current_era = 1 + TotalUnbondingPools::::get(); CurrentEra::set(current_era); - assert_ok!(Pools::unbond(Origin::signed(10), 10)); + assert_ok!(fully_unbond_permissioned(10)); // Then assert_eq!( @@ -1541,15 +1545,15 @@ mod unbond { // When the nominator trys to kick, then its a noop assert_noop!( - Pools::unbond(Origin::signed(901), 100), + Pools::fully_unbond(Origin::signed(901), 100), Error::::NotKickerOrDestroying ); // When the root kicks then its ok - assert_ok!(Pools::unbond(Origin::signed(900), 100)); + assert_ok!(Pools::fully_unbond(Origin::signed(900), 100)); // When the state toggler kicks then its ok - assert_ok!(Pools::unbond(Origin::signed(902), 200)); + assert_ok!(Pools::fully_unbond(Origin::signed(902), 200)); assert_eq!( BondedPool::::get(1).unwrap(), @@ -1582,7 +1586,7 @@ mod unbond { } #[test] - fn unbond_with_non_admins_works() { + fn unbond_permissionless_works() { // Scenarios where non-admin accounts can unbond others ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given the pool is blocked @@ -1590,33 +1594,36 @@ mod unbond { // A permissionless unbond attempt errors assert_noop!( - Pools::unbond(Origin::signed(420), 100), + Pools::fully_unbond(Origin::signed(420), 100), Error::::NotKickerOrDestroying ); // Given the pool is destroying unsafe_set_state(1, PoolState::Destroying).unwrap(); - // The depositor cannot be unbonded until they are the last delegator + // The depositor cannot be fully unbonded until they are the last delegator assert_noop!( - Pools::unbond(Origin::signed(420), 10), + Pools::fully_unbond(Origin::signed(10), 10), Error::::NotOnlyDelegator ); // Any account can unbond a delegator that is not the depositor - assert_ok!(Pools::unbond(Origin::signed(420), 100)); + assert_ok!(Pools::fully_unbond(Origin::signed(420), 100)); // Given the pool is blocked unsafe_set_state(1, PoolState::Blocked).unwrap(); // The depositor cannot be unbonded - assert_noop!(Pools::unbond(Origin::signed(420), 10), Error::::NotDestroying); + assert_noop!( + Pools::fully_unbond(Origin::signed(420), 10), + Error::::DoesNotHavePermission + ); // Given the pools is destroying unsafe_set_state(1, PoolState::Destroying).unwrap(); - // The depositor can be unbonded - assert_ok!(Pools::unbond(Origin::signed(420), 10)); + // The depositor can be unbonded by anyone. + assert_ok!(Pools::fully_unbond(Origin::signed(420), 10)); assert_eq!(BondedPools::::get(1).unwrap().points, 0); assert_eq!( @@ -1642,20 +1649,15 @@ mod unbond { fn unbond_errors_correctly() { ExtBuilder::default().build_and_execute(|| { assert_noop!( - Pools::unbond(Origin::signed(11), 11), + Pools::fully_unbond(Origin::signed(11), 11), Error::::DelegatorNotFound ); // Add the delegator - let delegator = Delegator { - pool_id: 2, - points: 10, - reward_pool_total_earnings: 0, - unbonding_era: None, - }; + let delegator = Delegator { pool_id: 2, points: 10, ..Default::default() }; Delegators::::insert(11, delegator); - let _ = Pools::unbond(Origin::signed(11), 11); + let _ = Pools::fully_unbond(Origin::signed(11), 11); }); } @@ -1663,12 +1665,7 @@ mod unbond { #[should_panic = "Defensive failure has been triggered!"] fn unbond_panics_when_reward_pool_not_found() { ExtBuilder::default().build_and_execute(|| { - let delegator = Delegator { - pool_id: 2, - points: 10, - reward_pool_total_earnings: 0, - unbonding_era: None, - }; + let delegator = Delegator { pool_id: 2, points: 10, ..Default::default() }; Delegators::::insert(11, delegator); BondedPool:: { id: 1, @@ -1681,9 +1678,203 @@ mod unbond { } .put(); - let _ = Pools::unbond(Origin::signed(11), 11); + let _ = Pools::fully_unbond(Origin::signed(11), 11); + }); + } + + #[test] + fn partial_unbond_era_tracking() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(Delegators::::get(10).unwrap().active_points(), 10); + assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 0); + assert_eq!(Delegators::::get(10).unwrap().pool_id, 1); + assert_eq!( + Delegators::::get(10).unwrap().unbonding_eras, + delegator_unbonding_eras!() + ); + assert_eq!(BondedPool::::get(1).unwrap().points, 10); + assert!(SubPoolsStorage::::get(1).is_none()); + assert_eq!(CurrentEra::get(), 0); + assert_eq!(BondingDuration::get(), 3); + + // so the depositor can leave, just keeps the test simpler. + unsafe_set_state(1, PoolState::Destroying).unwrap(); + + // when: casual unbond + assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); + + // then + assert_eq!(Delegators::::get(10).unwrap().active_points(), 9); + assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 1); + assert_eq!(BondedPool::::get(1).unwrap().points, 9); + assert_eq!( + Delegators::::get(10).unwrap().unbonding_eras, + delegator_unbonding_eras!(3 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 1, balance: 1 } + } + } + ); + + // when: casual further unbond, same era. + assert_ok!(Pools::unbond(Origin::signed(10), 10, 5)); + + // then + assert_eq!(Delegators::::get(10).unwrap().active_points(), 4); + assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 6); + assert_eq!(BondedPool::::get(1).unwrap().points, 4); + assert_eq!( + Delegators::::get(10).unwrap().unbonding_eras, + delegator_unbonding_eras!(3 => 6) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 } + } + } + ); + + // when: casual further unbond, next era. + CurrentEra::set(1); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); + + // then + assert_eq!(Delegators::::get(10).unwrap().active_points(), 3); + assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 7); + assert_eq!(BondedPool::::get(1).unwrap().points, 3); + assert_eq!( + Delegators::::get(10).unwrap().unbonding_eras, + delegator_unbonding_eras!(3 => 6, 4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 }, + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + + // when: unbonding more than our active: error + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 5), + Error::::NotEnoughPointsToUnbond + ); + // instead: + assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); + + // then + assert_eq!(Delegators::::get(10).unwrap().active_points(), 0); + assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 10); + assert_eq!(BondedPool::::get(1).unwrap().points, 0); + assert_eq!( + Delegators::::get(10).unwrap().unbonding_eras, + delegator_unbonding_eras!(3 => 6, 4 => 4) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 }, + 4 => UnbondPool { points: 4, balance: 4 } + } + } + ); }); } + + #[test] + fn partial_unbond_max_chunks() { + ExtBuilder::default().ed(1).build_and_execute(|| { + // so the depositor can leave, just keeps the test simpler. + unsafe_set_state(1, PoolState::Destroying).unwrap(); + MaxUnbonding::set(2); + + // given + assert_ok!(Pools::unbond(Origin::signed(10), 10, 2)); + CurrentEra::set(1); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); + assert_eq!( + Delegators::::get(10).unwrap().unbonding_eras, + delegator_unbonding_eras!(3 => 2, 4 => 3) + ); + + // when + CurrentEra::set(2); + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 4), + Error::::MaxUnbondingLimit + ); + + // when + MaxUnbonding::set(3); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); + assert_eq!( + Delegators::::get(10).unwrap().unbonding_eras, + delegator_unbonding_eras!(3 => 2, 4 => 3, 5 => 1) + ); + }) + } + + // depositor can unbond inly up to `MinCreateBond`. + #[test] + fn depositor_permissioned_partial_unbond() { + ExtBuilder::default() + .ed(1) + .add_delegators(vec![(100, 100)]) + .build_and_execute(|| { + // given + assert_eq!(MinCreateBond::::get(), 2); + assert_eq!(Delegators::::get(10).unwrap().active_points(), 10); + assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 0); + + // can unbond a bit.. + assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); + assert_eq!(Delegators::::get(10).unwrap().active_points(), 7); + assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 3); + + // but not less than 2 + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 6), + Error::::NotOnlyDelegator + ); + }); + } + + // same as above, but the pool is slashed and therefore the depositor cannot partially unbond. + #[test] + fn depositor_permissioned_partial_unbond_slashed() { + ExtBuilder::default() + .ed(1) + .add_delegators(vec![(100, 100)]) + .build_and_execute(|| { + // given + assert_eq!(MinCreateBond::::get(), 2); + assert_eq!(Delegators::::get(10).unwrap().active_points(), 10); + assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 0); + + // slash the default pool + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 5); + + // cannot unbond even 7, because the value of shares is now less. + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 7), + Error::::NotOnlyDelegator + ); + }); + } } mod pool_withdraw_unbonded { @@ -1712,6 +1903,7 @@ mod pool_withdraw_unbonded { mod withdraw_unbonded { use super::*; + use frame_support::bounded_btree_map; #[test] fn withdraw_unbonded_works_against_slashed_no_era_sub_pool() { @@ -1720,15 +1912,15 @@ mod withdraw_unbonded { .build_and_execute(|| { // Given assert_eq!(StakingMock::bonding_duration(), 3); - assert_ok!(Pools::unbond(Origin::signed(550), 550)); - assert_ok!(Pools::unbond(Origin::signed(40), 40)); + assert_ok!(Pools::fully_unbond(Origin::signed(550), 550)); + assert_ok!(Pools::fully_unbond(Origin::signed(40), 40)); assert_eq!(Balances::free_balance(&default_bonded_account()), 600); let mut current_era = 1; CurrentEra::set(current_era); // In a new era, unbond the depositor unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond(Origin::signed(10), 10)); + assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); let mut sub_pools = SubPoolsStorage::::get(1).unwrap(); let unbond_pool = sub_pools.with_era.get_mut(&(current_era + 3)).unwrap(); @@ -1812,10 +2004,10 @@ mod withdraw_unbonded { Balances::make_free_balance_be(&default_bonded_account(), 100); assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(100)); - assert_ok!(Pools::unbond(Origin::signed(40), 40)); - assert_ok!(Pools::unbond(Origin::signed(550), 550)); + assert_ok!(Pools::fully_unbond(Origin::signed(40), 40)); + assert_ok!(Pools::fully_unbond(Origin::signed(550), 550)); unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond(Origin::signed(10), 10)); + assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); SubPoolsStorage::::insert( 1, @@ -1873,7 +2065,7 @@ mod withdraw_unbonded { assert_eq!(Balances::free_balance(&10), 5); assert_eq!(Balances::free_balance(&default_bonded_account()), 10); unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond(Origin::signed(10), 10)); + assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); // Simulate a slash that is not accounted for in the sub pools. Balances::make_free_balance_be(&default_bonded_account(), 5); @@ -1909,28 +2101,17 @@ mod withdraw_unbonded { Error::::DelegatorNotFound ); - let mut delegator = Delegator { - pool_id: 1, - points: 10, - reward_pool_total_earnings: 0, - unbonding_era: None, - }; + let mut delegator = Delegator { pool_id: 1, points: 10, ..Default::default() }; Delegators::::insert(11, delegator.clone()); - // The delegator has not called `unbond` - assert_noop!( - Pools::withdraw_unbonded(Origin::signed(11), 11, 0), - Error::::NotUnbonding - ); - // Simulate calling `unbond` - delegator.unbonding_era = Some(0 + 3); + delegator.unbonding_eras = delegator_unbonding_eras!(3 + 0 => 10); Delegators::::insert(11, delegator.clone()); // We are still in the bonding duration assert_noop!( Pools::withdraw_unbonded(Origin::signed(11), 11, 0), - Error::::NotUnbondedYet + Error::::CannotWithdrawAny ); // If we error the delegator does not get removed @@ -1946,8 +2127,8 @@ mod withdraw_unbonded { .add_delegators(vec![(100, 100), (200, 200)]) .build_and_execute(|| { // Given - assert_ok!(Pools::unbond(Origin::signed(100), 100)); - assert_ok!(Pools::unbond(Origin::signed(200), 200)); + assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); + assert_ok!(Pools::fully_unbond(Origin::signed(200), 200)); assert_eq!( BondedPool::::get(1).unwrap(), BondedPool { @@ -1995,7 +2176,7 @@ mod withdraw_unbonded { fn withdraw_unbonded_destroying_permissionless() { ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given - assert_ok!(Pools::unbond(Origin::signed(100), 100)); + assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); assert_eq!( BondedPool::::get(1).unwrap(), BondedPool { @@ -2013,7 +2194,7 @@ mod withdraw_unbonded { // Cannot permissionlessly withdraw assert_noop!( - Pools::unbond(Origin::signed(420), 100), + Pools::fully_unbond(Origin::signed(420), 100), Error::::NotKickerOrDestroying ); @@ -2035,14 +2216,14 @@ mod withdraw_unbonded { .add_delegators(vec![(100, 100), (200, 200)]) .build_and_execute(|| { // Given - assert_ok!(Pools::unbond(Origin::signed(100), 100)); + assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); let mut current_era = 1; CurrentEra::set(current_era); - assert_ok!(Pools::unbond(Origin::signed(200), 200)); + assert_ok!(Pools::fully_unbond(Origin::signed(200), 200)); unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond(Origin::signed(10), 10)); + assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); assert_eq!( SubPoolsStorage::::get(1).unwrap(), @@ -2054,6 +2235,7 @@ mod withdraw_unbonded { } } ); + // Skip ahead eras to where its valid for the delegators to withdraw current_era += StakingMock::bonding_duration(); CurrentEra::set(current_era); @@ -2077,9 +2259,9 @@ mod withdraw_unbonded { } ); - // Cannot withdraw if their is another delegator in the depositors `with_era` pool + // Cannot withdraw the depositor if their is a delegator in another `with_era` pool. assert_noop!( - Pools::unbond(Origin::signed(420), 10), + Pools::withdraw_unbonded(Origin::signed(420), 10, 0), Error::::NotOnlyDelegator ); @@ -2110,9 +2292,9 @@ mod withdraw_unbonded { fn withdraw_unbonded_depositor_no_era_pool() { ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { // Given - assert_ok!(Pools::unbond(Origin::signed(100), 100)); + assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::unbond(Origin::signed(10), 10)); + assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); // Skip ahead to an era where the `with_era` pools can get merged into the `no_era` // pool. let current_era = TotalUnbondingPools::::get(); @@ -2156,6 +2338,186 @@ mod withdraw_unbonded { assert!(!BondedPools::::contains_key(1)); }); } + + #[test] + fn partial_withdraw_unbonded_depositor() { + ExtBuilder::default().ed(1).build_and_execute(|| { + // so the depositor can leave, just keeps the test simpler. + unsafe_set_state(1, PoolState::Destroying).unwrap(); + + // given + assert_ok!(Pools::unbond(Origin::signed(10), 10, 6)); + CurrentEra::set(1); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); + assert_eq!( + Delegators::::get(10).unwrap().unbonding_eras, + delegator_unbonding_eras!(3 => 6, 4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 }, + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!(Delegators::::get(10).unwrap().active_points(), 3); + assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 7); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { delegator: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { delegator: 10, pool_id: 1, payout: 0 }, + Event::Unbonded { delegator: 10, pool_id: 1, amount: 6 }, + Event::PaidOut { delegator: 10, pool_id: 1, payout: 0 }, + Event::Unbonded { delegator: 10, pool_id: 1, amount: 1 } + ] + ); + + // when + CurrentEra::set(2); + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(10), 10, 0), + Error::::CannotWithdrawAny + ); + + // when + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + + // then + assert_eq!( + Delegators::::get(10).unwrap().unbonding_eras, + delegator_unbonding_eras!(4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { delegator: 10, pool_id: 1, amount: 6 }] + ); + + // when + CurrentEra::set(4); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + + // then + assert_eq!( + Delegators::::get(10).unwrap().unbonding_eras, + delegator_unbonding_eras!() + ); + assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { delegator: 10, pool_id: 1, amount: 1 },] + ); + + // when repeating: + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(10), 10, 0), + Error::::CannotWithdrawAny + ); + }); + } + + #[test] + fn partial_withdraw_unbonded_non_depositor() { + ExtBuilder::default().add_delegators(vec![(11, 10)]).build_and_execute(|| { + // given + assert_ok!(Pools::unbond(Origin::signed(11), 11, 6)); + CurrentEra::set(1); + assert_ok!(Pools::unbond(Origin::signed(11), 11, 1)); + assert_eq!( + Delegators::::get(11).unwrap().unbonding_eras, + delegator_unbonding_eras!(3 => 6, 4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 }, + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!(Delegators::::get(11).unwrap().active_points(), 3); + assert_eq!(Delegators::::get(11).unwrap().unbonding_points(), 7); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { delegator: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { delegator: 11, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { delegator: 11, pool_id: 1, payout: 0 }, + Event::Unbonded { delegator: 11, pool_id: 1, amount: 6 }, + Event::PaidOut { delegator: 11, pool_id: 1, payout: 0 }, + Event::Unbonded { delegator: 11, pool_id: 1, amount: 1 } + ] + ); + + // when + CurrentEra::set(2); + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(11), 11, 0), + Error::::CannotWithdrawAny + ); + + // when + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(11), 11, 0)); + + // then + assert_eq!( + Delegators::::get(11).unwrap().unbonding_eras, + delegator_unbonding_eras!(4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { delegator: 11, pool_id: 1, amount: 6 }] + ); + + // when + CurrentEra::set(4); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(11), 11, 0)); + + // then + assert_eq!( + Delegators::::get(11).unwrap().unbonding_eras, + delegator_unbonding_eras!() + ); + assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { delegator: 11, pool_id: 1, amount: 1 }] + ); + + // when repeating: + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(11), 11, 0), + Error::::CannotWithdrawAny + ); + }); + } } mod create { @@ -2185,12 +2547,7 @@ mod create { assert_eq!(Balances::free_balance(&11), 0); assert_eq!( Delegators::::get(11).unwrap(), - Delegator { - pool_id: 2, - points: StakingMock::minimum_bond(), - reward_pool_total_earnings: Zero::zero(), - unbonding_era: None - } + Delegator { pool_id: 2, points: StakingMock::minimum_bond(), ..Default::default() } ); assert_eq!( BondedPool::::get(2).unwrap(), diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 4484ff6b6b295..2d2944cc9a44f 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -87,6 +87,8 @@ pub use self::{ StorageHasher, Twox128, Twox256, Twox64Concat, }, storage::{ + bounded_btree_map::BoundedBTreeMap, + bounded_btree_set::BoundedBTreeSet, bounded_vec::{BoundedSlice, BoundedVec}, migration, weak_bounded_vec::WeakBoundedVec, @@ -138,6 +140,25 @@ macro_rules! bounded_vec { } } +/// Build a bounded btree-map from the given literals. +/// +/// The type of the outcome must be known. +/// +/// Will not handle any errors and just panic if the given literals cannot fit in the corresponding +/// bounded vec type. Thus, this is only suitable for testing and non-consensus code. +#[macro_export] +#[cfg(feature = "std")] +macro_rules! bounded_btree_map { + ($ ( $key:expr => $value:expr ),* $(,)?) => { + { + $crate::traits::TryCollect::<$crate::BoundedBTreeMap<_, _, _>>::try_collect( + $crate::sp_std::vec![$(($key, $value)),*].into_iter() + ).unwrap() + } + }; + +} + /// Generate a new type alias for [`storage::types::StorageValue`], /// [`storage::types::StorageMap`], [`storage::types::StorageDoubleMap`] /// and [`storage::types::StorageNMap`]. From 3d293a9b16d1521b9b92400dd19f29ff7bf6f2ac Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Thu, 21 Apr 2022 18:25:05 -0400 Subject: [PATCH 286/299] Update Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 1060a44eccd4f..3149d402efc7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6207,7 +6207,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-bags-list", "pallet-balances", "pallet-nomination-pools", From 7aa9f431d9616accbe308bf3cddb4cbda39bdb2c Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 21 Apr 2022 15:42:42 -0700 Subject: [PATCH 287/299] Rename Delegator to PoolMember --- Cargo.lock | 2 +- .../nomination-pools/benchmarking/src/lib.rs | 56 +- .../nomination-pools/benchmarking/src/mock.rs | 4 +- frame/nomination-pools/src/lib.rs | 578 +++++++++--------- frame/nomination-pools/src/mock.rs | 20 +- frame/nomination-pools/src/tests.rs | 518 ++++++++-------- frame/nomination-pools/src/weights.rs | 34 +- 7 files changed, 606 insertions(+), 606 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1060a44eccd4f..3149d402efc7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6207,7 +6207,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-bags-list", "pallet-balances", "pallet-nomination-pools", diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 50dd97d2cd1a5..aa066d2aaa8d7 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -8,8 +8,8 @@ use frame_election_provider_support::SortedListProvider; use frame_support::{ensure, traits::Get}; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ - BalanceOf, BondExtra, BondedPoolInner, BondedPools, ConfigOp, Delegators, MaxDelegators, - MaxDelegatorsPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, Pallet as Pools, + BalanceOf, BondExtra, BondedPoolInner, BondedPools, ConfigOp, PoolMembers, MaxPoolMembers, + MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, Pallet as Pools, PoolRoles, PoolState, RewardPools, SubPoolsStorage, }; use sp_runtime::traits::{Bounded, Zero}; @@ -78,7 +78,7 @@ struct ListScenario { origin1: T::AccountId, creator1: T::AccountId, dest_weight: BalanceOf, - origin1_delegator: Option, + origin1_member: Option, } impl ListScenario { @@ -146,7 +146,7 @@ impl ListScenario { origin1: pool_origin1, creator1: pool_creator1, dest_weight, - origin1_delegator: None, + origin1_member: None, }) } @@ -158,12 +158,12 @@ impl ListScenario { .max(amount); let joiner: T::AccountId = account("joiner", USER_SEED, 0); - self.origin1_delegator = Some(joiner.clone()); + self.origin1_member = Some(joiner.clone()); CurrencyOf::::make_free_balance_be(&joiner, amount * 2u32.into()); let original_bonded = T::StakingInterface::active_stake(&self.origin1).unwrap(); - // Unbond `amount` from the underlying pool account so when the delegator joins + // Unbond `amount` from the underlying pool account so when the member joins // we will maintain `current_bonded`. T::StakingInterface::unbond(self.origin1.clone(), amount) .expect("the pool was created in `Self::new`."); @@ -179,10 +179,10 @@ impl ListScenario { let weight_of = pallet_staking::Pallet::::weight_of_fn(); assert_eq!(vote_to_balance::(weight_of(&self.origin1)).unwrap(), original_bonded); - // Sanity check the delegator was added correctly - let delegator = Delegators::::get(&joiner).unwrap(); - assert_eq!(delegator.points, amount); - assert_eq!(delegator.pool_id, 1); + // Sanity check the member was added correctly + let member = PoolMembers::::get(&joiner).unwrap(); + assert_eq!(member.points, amount); + assert_eq!(member.pool_id, 1); self } @@ -292,24 +292,24 @@ frame_benchmarking::benchmarks! { let amount = origin_weight - scenario.dest_weight.clone(); let scenario = scenario.add_joiner(amount); - let delegator_id = scenario.origin1_delegator.unwrap().clone(); - let all_points = Delegators::::get(&delegator_id).unwrap().points; - whitelist_account!(delegator_id); - }: _(Origin::Signed(delegator_id.clone()), delegator_id.clone(), all_points) + let member_id = scenario.origin1_member.unwrap().clone(); + let all_points = PoolMembers::::get(&member_id).unwrap().points; + whitelist_account!(member_id); + }: _(Origin::Signed(member_id.clone()), member_id.clone(), all_points) verify { let bonded_after = T::StakingInterface::active_stake(&scenario.origin1).unwrap(); // We at least went down to the destination bag assert!(bonded_after <= scenario.dest_weight.clone()); - let delegator = Delegators::::get( - &delegator_id + let member = PoolMembers::::get( + &member_id ) .unwrap(); assert_eq!( - delegator.unbonding_eras.keys().cloned().collect::>(), + member.unbonding_eras.keys().cloned().collect::>(), vec![0 + T::StakingInterface::bonding_duration()] ); assert_eq!( - delegator.unbonding_eras.values().cloned().collect::>(), + member.unbonding_eras.values().cloned().collect::>(), vec![all_points] ); } @@ -322,7 +322,7 @@ frame_benchmarking::benchmarks! { .max(CurrencyOf::::minimum_balance()); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); - // Add a new delegator + // Add a new member let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, 1) @@ -335,7 +335,7 @@ frame_benchmarking::benchmarks! { ); assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); - // Unbond the new delegator + // Unbond the new member Pools::::fully_unbond(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); // Sanity check that unbond worked @@ -366,7 +366,7 @@ frame_benchmarking::benchmarks! { .max(CurrencyOf::::minimum_balance()); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); - // Add a new delegator + // Add a new member let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, 1) @@ -379,7 +379,7 @@ frame_benchmarking::benchmarks! { ); assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); - // Unbond the new delegator + // Unbond the new member pallet_staking::CurrentEra::::put(0); Pools::::fully_unbond(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); @@ -451,7 +451,7 @@ frame_benchmarking::benchmarks! { assert!(BondedPools::::contains_key(&1)); assert!(SubPoolsStorage::::contains_key(&1)); assert!(RewardPools::::contains_key(&1)); - assert!(Delegators::::contains_key(&depositor)); + assert!(PoolMembers::::contains_key(&depositor)); assert!(frame_system::Account::::contains_key(&reward_account)); whitelist_account!(depositor); @@ -462,7 +462,7 @@ frame_benchmarking::benchmarks! { assert!(!BondedPools::::contains_key(&1)); assert!(!SubPoolsStorage::::contains_key(&1)); assert!(!RewardPools::::contains_key(&1)); - assert!(!Delegators::::contains_key(&depositor)); + assert!(!PoolMembers::::contains_key(&depositor)); assert!(!frame_system::Account::::contains_key(&pool_account)); assert!(!frame_system::Account::::contains_key(&reward_account)); @@ -504,7 +504,7 @@ frame_benchmarking::benchmarks! { BondedPoolInner { points: min_create_bond, state: PoolState::Open, - delegator_counter: 1, + member_counter: 1, roles: PoolRoles { depositor: depositor.clone(), root: depositor.clone(), @@ -545,7 +545,7 @@ frame_benchmarking::benchmarks! { BondedPoolInner { points: min_create_bond, state: PoolState::Open, - delegator_counter: 1, + member_counter: 1, roles: PoolRoles { depositor: depositor.clone(), root: depositor.clone(), @@ -608,8 +608,8 @@ frame_benchmarking::benchmarks! { assert_eq!(MinJoinBond::::get(), BalanceOf::::max_value()); assert_eq!(MinCreateBond::::get(), BalanceOf::::max_value()); assert_eq!(MaxPools::::get(), Some(u32::MAX)); - assert_eq!(MaxDelegators::::get(), Some(u32::MAX)); - assert_eq!(MaxDelegatorsPerPool::::get(), Some(u32::MAX)); + assert_eq!(MaxPoolMembers::::get(), Some(u32::MAX)); + assert_eq!(MaxPoolMembersPerPool::::get(), Some(u32::MAX)); } impl_benchmark_test_suite!( diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 94b7f300099a4..d98bcb542f514 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -192,8 +192,8 @@ pub fn new_test_ext() -> sp_io::TestExternalities { min_join_bond: 2, min_create_bond: 2, max_pools: Some(3), - max_delegators_per_pool: Some(3), - max_delegators: Some(3 * 3), + max_members_per_pool: Some(3), + max_members: Some(3 * 3), } .assimilate_storage(&mut storage); sp_io::TestExternalities::from(storage) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 47655454c5fde..1aeb02d16f423 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -17,8 +17,8 @@ //! # Nomination Pools for Staking Delegation //! -//! A pallet that allows delegators to delegate their stake to nominating pools. A nomination pool -//! acts as nominator and nominates validators on the delegators behalf. +//! A pallet that allows members to delegate their stake to nominating pools. A nomination pool +//! acts as nominator and nominates validators on the members behalf. //! //! # Index //! @@ -34,10 +34,10 @@ //! [`RewardPools`]. Reward pools are identified via the pools bonded account. //! * unbonding sub pools: Collection of pools at different phases of the unbonding lifecycle. See //! [`SubPools`] and [`SubPoolsStorage`]. Sub pools are identified via the pools bonded account. -//! * delegators: Accounts that are members of pools. See [`Delegator`] and [`Delegators`]. -//! Delegators are identified via their account. -//! * point: A unit of measure for a delegators portion of a pool's funds. -//! * kick: The act of a pool administrator forcibly ejecting a delegator. +//! * members: Accounts that are members of pools. See [`PoolMember`] and [`PoolMembers`]. +//! PoolMembers are identified via their account. +//! * point: A unit of measure for a members portion of a pool's funds. +//! * kick: The act of a pool administrator forcibly ejecting a member. //! //! ## Usage //! @@ -47,18 +47,18 @@ //! //! ### Claim rewards //! -//! After joining a pool, a delegator can claim rewards by calling [`Call::claim_payout`]. +//! After joining a pool, a member can claim rewards by calling [`Call::claim_payout`]. //! //! For design docs see the [reward pool](#reward-pool) section. //! //! ### Leave //! -//! In order to leave, a delegator must take two steps. +//! In order to leave, a member must take two steps. //! //! First, they must call [`Call::unbond`]. The unbond other extrinsic will start the -//! unbonding process by unbonding all of the delegators funds. +//! unbonding process by unbonding all of the members funds. //! -//! Second, once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the delegator +//! Second, once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the member //! can call [`Call::withdraw_unbonded`] to withdraw all their funds. //! //! For design docs see the [bonded pool](#bonded-pool) and [unbonding sub @@ -67,8 +67,8 @@ //! ### Slashes //! //! Slashes are distributed evenly across the bonded pool and the unbonding pools from slash era+1 -//! through the slash apply era. Thus, any delegator who either a) unbonded or b) was actively -//! bonded in the aforementioned range of eras will be affected by the slash. A delegator is slashed +//! through the slash apply era. Thus, any member who either a) unbonded or b) was actively +//! bonded in the aforementioned range of eras will be affected by the slash. A member is slashed //! pro-rata based on its stake relative to the total slash amount. //! //! For design docs see the [slashing](#slashing) section. @@ -81,18 +81,18 @@ //! //! To help facilitate pool administration the pool has one of three states (see [`PoolState`]): //! -//! * Open: Anyone can join the pool and no delegators can be permissionlessly removed. -//! * Blocked: No delegators can join and some admin roles can kick delegators. -//! * Destroying: No delegators can join and all delegators can be permissionlessly removed with +//! * Open: Anyone can join the pool and no members can be permissionlessly removed. +//! * Blocked: No members can join and some admin roles can kick members. +//! * Destroying: No members can join and all members can be permissionlessly removed with //! [`Call::unbond`] and [`Call::withdraw_unbonded`]. Once a pool is in destroying state, it //! cannot be reverted to another state. //! //! A pool has 3 administrative roles (see [`PoolRoles`]): //! -//! * Depositor: creates the pool and is the initial delegator. They can only leave the pool once -//! all other delegators have left. Once they fully leave the pool is destroyed. +//! * Depositor: creates the pool and is the initial member. They can only leave the pool once +//! all other members have left. Once they fully leave the pool is destroyed. //! * Nominator: can select which validators the pool nominates. -//! * State-Toggler: can change the pools state and kick delegators if the pool is blocked. +//! * State-Toggler: can change the pools state and kick members if the pool is blocked. //! * Root: can change the nominator, state-toggler, or itself and can perform any of the actions //! the nominator or state-toggler can. //! @@ -105,11 +105,11 @@ //! ### Goals //! //! * Maintain network security by upholding integrity of slashing events, sufficiently penalizing -//! delegators that where in the pool while it was backing a validator that got slashed. -//! * Maximize scalability in terms of delegator count. +//! members that where in the pool while it was backing a validator that got slashed. +//! * Maximize scalability in terms of member count. //! -//! In order to maintain scalability, all operations are independent of the number of delegators. To -//! do this, delegation specific information is stored local to the delegator while the pool data +//! In order to maintain scalability, all operations are independent of the number of members. To +//! do this, delegation specific information is stored local to the member while the pool data //! structures have bounded datum. //! //! ### Bonded pool @@ -118,12 +118,12 @@ //! unbonding. The total points of a bonded pool are always equal to the sum of points of the //! delegation members. A bonded pool tracks its points and reads its bonded balance. //! -//! When a delegator joins a pool, `amount_transferred` is transferred from the delegators account +//! When a member joins a pool, `amount_transferred` is transferred from the members account //! to the bonded pools account. Then the pool calls `staking::bond_extra(amount_transferred)` and -//! issues new points which are tracked by the delegator and added to the bonded pool's points. +//! issues new points which are tracked by the member and added to the bonded pool's points. //! //! When the pool already has some balance, we want the value of a point before the transfer to -//! equal the value of a point after the transfer. So, when a delegator joins a bonded pool with a +//! equal the value of a point after the transfer. So, when a member joins a bonded pool with a //! given `amount_transferred`, we maintain the ratio of bonded balance to points such that: //! //! ```text @@ -156,16 +156,16 @@ //! * The total earnings ever at the time of the last payout: `reward_pool.total_earnings` //! * The total points in the pool at the time of the last payout: `reward_pool.points` //! -//! And the delegator needs to store: +//! And the member needs to store: //! -//! * The total payouts at the time of the last payout by that delegator: -//! `delegator.reward_pool_total_earnings` +//! * The total payouts at the time of the last payout by that member: +//! `member.reward_pool_total_earnings` //! //! Before the first reward claim is initiated for a pool, all the above variables are set to zero. //! -//! When a delegator initiates a claim, the following happens: +//! When a member initiates a claim, the following happens: //! -//! 1) Compute the reward pool's total points and the delegator's virtual points in the reward pool +//! 1) Compute the reward pool's total points and the member's virtual points in the reward pool //! * First `current_total_earnings` is computed (`current_balance` is the free balance of the //! reward pool at the beginning of these operations.) //! ```text @@ -177,40 +177,40 @@ //! `bonding_pool.total_points`. In other words, for every unit of balance that has been //! earned by the reward pool, the reward pool points are inflated by `bonded_pool.points`. In //! effect this allows each, single unit of balance (e.g. planck) to be divvied up pro-rata -//! among delegators based on points. +//! among members based on points. //! ```text //! new_earnings = current_total_earnings - reward_pool.total_earnings; //! current_points = reward_pool.points + bonding_pool.points * new_earnings; //! ``` -//! * Finally, the`delegator_virtual_points` are computed: the product of the delegator's points +//! * Finally, the`member_virtual_points` are computed: the product of the member's points //! in the bonding pool and the total inflow of balance units since the last time the -//! delegator claimed rewards +//! member claimed rewards //! ```text -//! new_earnings_since_last_claim = current_total_earnings - delegator.reward_pool_total_earnings; -//! delegator_virtual_points = delegator.points * new_earnings_since_last_claim; +//! new_earnings_since_last_claim = current_total_earnings - member.reward_pool_total_earnings; +//! member_virtual_points = member.points * new_earnings_since_last_claim; //! ``` -//! 2) Compute the `delegator_payout`: +//! 2) Compute the `member_payout`: //! ```text -//! delegator_pool_point_ratio = delegator_virtual_points / current_points; -//! delegator_payout = current_balance * delegator_pool_point_ratio; +//! member_pool_point_ratio = member_virtual_points / current_points; +//! member_payout = current_balance * member_pool_point_ratio; //! ``` -//! 3) Transfer `delegator_payout` to the delegator -//! 4) For the delegator set: +//! 3) Transfer `member_payout` to the member +//! 4) For the member set: //! ```text -//! delegator.reward_pool_total_earnings = current_total_earnings; +//! member.reward_pool_total_earnings = current_total_earnings; //! ``` //! 5) For the pool set: //! ```text -//! reward_pool.points = current_points - delegator_virtual_points; -//! reward_pool.balance = current_balance - delegator_payout; +//! reward_pool.points = current_points - member_virtual_points; +//! reward_pool.balance = current_balance - member_payout; //! reward_pool.total_earnings = current_total_earnings; //! ``` //! //! _Note_: One short coming of this design is that new joiners can claim rewards for the era after //! they join even though their funds did not contribute to the pools vote weight. When a -//! delegator joins, it's `reward_pool_total_earnings` field is set equal to the `total_earnings` +//! member joins, it's `reward_pool_total_earnings` field is set equal to the `total_earnings` //! of the reward pool at that point in time. At best the reward pool has the rewards up through the -//! previous era. If a delegator joins prior to the election snapshot it will benefit from the +//! previous era. If a member joins prior to the election snapshot it will benefit from the //! rewards for the active era despite not contributing to the pool's vote weight. If it joins //! after the election snapshot is taken it will benefit from the rewards of the next _2_ eras //! because it's vote weight will not be counted until the election snapshot in active era + 1. @@ -226,22 +226,22 @@ //! //! ### Unbonding sub pools //! -//! When a delegator unbonds, it's balance is unbonded in the bonded pool's account and tracked in +//! When a member unbonds, it's balance is unbonded in the bonded pool's account and tracked in //! an unbonding pool associated with the active era. If no such pool exists, one is created. To -//! track which unbonding sub pool a delegator belongs too, a delegator tracks it's +//! track which unbonding sub pool a member belongs too, a member tracks it's //! `unbonding_era`. //! -//! When a delegator initiates unbonding it's claim on the bonded pool +//! When a member initiates unbonding it's claim on the bonded pool //! (`balance_to_unbond`) is computed as: //! //! ```text -//! balance_to_unbond = (bonded_pool.balance / bonded_pool.points) * delegator.points; +//! balance_to_unbond = (bonded_pool.balance / bonded_pool.points) * member.points; //! ``` //! //! If this is the first transfer into an unbonding pool arbitrary amount of points can be issued //! per balance. In this implementation unbonding pools are initialized with a 1 point to 1 balance //! ratio (see [`POINTS_TO_BALANCE_INIT_RATIO`]). Otherwise, the unbonding pools hold the same -//! points to balance ratio properties as the bonded pool, so delegator points in the +//! points to balance ratio properties as the bonded pool, so member points in the //! unbonding pool are issued based on //! //! ```text @@ -259,7 +259,7 @@ //! //! This scheme "averages" out the points value in the unbonded pool. //! -//! Once a delegators `unbonding_era` is older than `current_era - +//! Once a members `unbonding_era` is older than `current_era - //! [sp_staking::StakingInterface::bonding_duration]`, it can can cash it's points out of the //! corresponding unbonding pool. If it's `unbonding_era` is older than `current_era - //! TotalUnbondingPools`, it can cash it's points from the unbonded pool. @@ -277,11 +277,11 @@ //! //! Unbonding pools need to be slashed to ensure all nominators whom where in the bonded pool //! while it was backing a validator that equivocated are punished. Without these measures a -//! delegator could unbond right after a validator equivocated with no consequences. +//! member could unbond right after a validator equivocated with no consequences. //! -//! This strategy is unfair to delegators who joined after the slash, because they get slashed as -//! well, but spares delegators who unbond. The latter is much more important for security: if a -//! pool's validators are attacking the network, their delegators need to unbond fast! Avoiding +//! This strategy is unfair to members who joined after the slash, because they get slashed as +//! well, but spares members who unbond. The latter is much more important for security: if a +//! pool's validators are attacking the network, their members need to unbond fast! Avoiding //! slashes gives them an incentive to do that if validators get repeatedly slashed. //! //! To be fair to joiners, this implementation also need joining pools, which are actively staking, @@ -294,10 +294,10 @@ //! //! ### Limitations //! -//! * Delegators cannot vote with their staked funds because they are transferred into the pools -//! account. In the future this can be overcome by allowing the delegators to vote with their +//! * PoolMembers cannot vote with their staked funds because they are transferred into the pools +//! account. In the future this can be overcome by allowing the members to vote with their //! bonded funds via vote splitting. -//! * Delegators cannot quickly transfer to another pool if they do no like nominations, instead +//! * PoolMembers cannot quickly transfer to another pool if they do no like nominations, instead //! they must wait for the unbonding duration. //! //! # Runtime builder warnings @@ -365,7 +365,7 @@ enum BondType { Later, } -/// How to increase the bond of a delegator. +/// How to increase the bond of a member. #[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] pub enum BondExtra { /// Take from the free balance. @@ -381,37 +381,37 @@ enum AccountType { Reward, } -/// A delegator in a pool. +/// A member in a pool. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, CloneNoBound)] #[cfg_attr(feature = "std", derive(PartialEqNoBound, DefaultNoBound))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] -pub struct Delegator { +pub struct PoolMember { /// The identifier of the pool to which `who` belongs. pub pool_id: PoolId, - /// The quantity of points this delegator has in the bonded pool or in a sub pool if + /// The quantity of points this member has in the bonded pool or in a sub pool if /// `Self::unbonding_era` is some. pub points: BalanceOf, - /// The reward pools total earnings _ever_ the last time this delegator claimed a payout. + /// The reward pools total earnings _ever_ the last time this member claimed a payout. /// Assuming no massive burning events, we expect this value to always be below total issuance. - /// This value lines up with the [`RewardPool::total_earnings`] after a delegator claims a + /// This value lines up with the [`RewardPool::total_earnings`] after a member claims a /// payout. pub reward_pool_total_earnings: BalanceOf, - /// The eras in which this delegator is unbonding, mapped from era index to the number of + /// The eras in which this member is unbonding, mapped from era index to the number of /// points scheduled to unbond in the given era. pub unbonding_eras: BoundedBTreeMap, T::MaxUnbonding>, } -impl Delegator { +impl PoolMember { #[cfg(any(test, debug_assertions))] fn total_points(&self) -> BalanceOf { self.active_points().saturating_add(self.unbonding_points()) } - /// Active balance of the delegator. + /// Active balance of the member. /// - /// This is derived from the ratio of points in the pool to which the delegator belongs to. - /// Might return different values based on the pool state for the same delegator and points. + /// This is derived from the ratio of points in the pool to which the member belongs to. + /// Might return different values based on the pool state for the same member and points. pub(crate) fn active_balance(&self) -> BalanceOf { if let Some(pool) = BondedPool::::get(self.pool_id).defensive() { pool.points_to_balance(self.points) @@ -420,12 +420,12 @@ impl Delegator { } } - /// Active points of the delegator. + /// Active points of the member. pub(crate) fn active_points(&self) -> BalanceOf { self.points } - /// Inactive points of the delegator, waiting to be withdrawn. + /// Inactive points of the member, waiting to be withdrawn. pub(crate) fn unbonding_points(&self) -> BalanceOf { self.unbonding_eras .as_ref() @@ -498,7 +498,7 @@ pub enum PoolState { Blocked, /// The pool is in the process of being destroyed. /// - /// All delegators can now be permissionlessly unbonded, and the pool can never go back to any + /// All members can now be permissionlessly unbonded, and the pool can never go back to any /// other state other than being dissolved. Destroying, } @@ -506,15 +506,15 @@ pub enum PoolState { /// Pool adminstration roles. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone)] pub struct PoolRoles { - /// Creates the pool and is the initial delegator. They can only leave the pool once all - /// other delegators have left. Once they fully leave, the pool is destroyed. + /// Creates the pool and is the initial member. They can only leave the pool once all + /// other members have left. Once they fully leave, the pool is destroyed. pub depositor: AccountId, /// Can change the nominator, state-toggler, or itself and can perform any of the actions /// the nominator or state-toggler can. pub root: AccountId, /// Can select which validators the pool nominates. pub nominator: AccountId, - /// Can change the pools state and kick delegators if the pool is blocked. + /// Can change the pools state and kick members if the pool is blocked. pub state_toggler: AccountId, } @@ -523,12 +523,12 @@ pub struct PoolRoles { #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct BondedPoolInner { - /// Total points of all the delegators in the pool who are actively bonded. + /// Total points of all the members in the pool who are actively bonded. pub points: BalanceOf, /// The current state of the pool. pub state: PoolState, - /// Count of delegators that belong to the pool. - pub delegator_counter: u32, + /// Count of members that belong to the pool. + pub member_counter: u32, /// See [`PoolRoles`]. pub roles: PoolRoles, } @@ -568,7 +568,7 @@ impl BondedPool { roles, state: PoolState::Open, points: Zero::zero(), - delegator_counter: Zero::zero(), + member_counter: Zero::zero(), }, } } @@ -637,25 +637,25 @@ impl BondedPool { balance } - /// Increment the delegator counter. Ensures that the pool and system delegator limits are + /// Increment the member counter. Ensures that the pool and system member limits are /// respected. - fn try_inc_delegators(&mut self) -> Result<(), DispatchError> { + fn try_inc_members(&mut self) -> Result<(), DispatchError> { ensure!( - MaxDelegatorsPerPool::::get() - .map_or(true, |max_per_pool| self.delegator_counter < max_per_pool), - Error::::MaxDelegators + MaxPoolMembersPerPool::::get() + .map_or(true, |max_per_pool| self.member_counter < max_per_pool), + Error::::MaxPoolMembers ); ensure!( - MaxDelegators::::get().map_or(true, |max| Delegators::::count() < max), - Error::::MaxDelegators + MaxPoolMembers::::get().map_or(true, |max| PoolMembers::::count() < max), + Error::::MaxPoolMembers ); - self.delegator_counter = self.delegator_counter.defensive_saturating_add(1); + self.member_counter = self.member_counter.defensive_saturating_add(1); Ok(()) } - /// Decrement the delegator counter. - fn dec_delegators(mut self) -> Self { - self.delegator_counter = self.delegator_counter.defensive_saturating_sub(1); + /// Decrement the member counter. + fn dec_members(mut self) -> Self { + self.member_counter = self.member_counter.defensive_saturating_sub(1); self } @@ -688,8 +688,8 @@ impl BondedPool { } fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf) -> bool { - // NOTE: if we add `&& self.delegator_counter == 1`, then this becomes even more strict and - // ensures that there are no unbonding delegators hanging around either. + // NOTE: if we add `&& self.member_counter == 1`, then this becomes even more strict and + // ensures that there are no unbonding members hanging around either. self.is_destroying() && self.points == alleged_depositor_points } @@ -734,24 +734,24 @@ impl BondedPool { &self, caller: &T::AccountId, target_account: &T::AccountId, - target_delegator: &Delegator, + target_member: &PoolMember, unbonding_points: BalanceOf, ) -> Result<(), DispatchError> { let is_permissioned = caller == target_account; let is_depositor = *target_account == self.roles.depositor; match (is_permissioned, is_depositor) { // If the pool is blocked, then an admin with kicking permissions can remove a - // delegator. If the pool is being destroyed, anyone can remove a delegator + // member. If the pool is being destroyed, anyone can remove a member (false, false) => { ensure!( self.can_kick(caller) || self.is_destroying(), Error::::NotKickerOrDestroying ) }, - // Any delegator who is not the depositor can always unbond themselves + // Any member who is not the depositor can always unbond themselves (true, false) => (), (_, true) => { - if self.is_destroying_and_only_depositor(target_delegator.active_points()) { + if self.is_destroying_and_only_depositor(target_member.active_points()) { // if the pool is about to be destroyed, anyone can unbond the depositor, and // they can fully unbond. } else { @@ -760,14 +760,14 @@ impl BondedPool { ensure!(is_permissioned, Error::::DoesNotHavePermission); let balance_after_unbond = { let new_depositor_points = - target_delegator.active_points().saturating_sub(unbonding_points); - let mut depositor_after_unbond = (*target_delegator).clone(); + target_member.active_points().saturating_sub(unbonding_points); + let mut depositor_after_unbond = (*target_member).clone(); depositor_after_unbond.points = new_depositor_points; depositor_after_unbond.active_balance() }; ensure!( balance_after_unbond >= MinCreateBond::::get(), - Error::::NotOnlyDelegator + Error::::NotOnlyPoolMember ); } }, @@ -782,15 +782,15 @@ impl BondedPool { &self, caller: &T::AccountId, target_account: &T::AccountId, - target_delegator: &Delegator, + target_member: &PoolMember, sub_pools: &SubPools, ) -> Result<(), DispatchError> { if *target_account == self.roles.depositor { ensure!( - sub_pools.sum_unbonding_points() == target_delegator.unbonding_points(), - Error::::NotOnlyDelegator + sub_pools.sum_unbonding_points() == target_member.unbonding_points(), + Error::::NotOnlyPoolMember ); - debug_assert_eq!(self.delegator_counter, 1, "only delegator must exist at this point"); + debug_assert_eq!(self.member_counter, 1, "only member must exist at this point"); Ok(()) } else { // This isn't a depositor @@ -1069,7 +1069,7 @@ pub mod pallet { >; /// The amount of eras a `SubPools::with_era` pool can exist before it gets merged into the - /// `SubPools::no_era` pool. In other words, this is the amount of eras a delegator will be + /// `SubPools::no_era` pool. In other words, this is the amount of eras a member will be /// able to withdraw from an unbonding pool which is guaranteed to have the correct ratio of /// points to balance; once the `with_era` pool is merged into the `no_era` pool, the ratio /// can become skewed due to some slashed ratio getting merged in at some point. @@ -1078,7 +1078,7 @@ pub mod pallet { /// The maximum length, in bytes, that a pools metadata maybe. type MaxMetadataLen: Get; - /// The maximum number of simultaneous unbonding chunks that can exist per delegator. + /// The maximum number of simultaneous unbonding chunks that can exist per member. type MaxUnbonding: Get; } @@ -1098,19 +1098,19 @@ pub mod pallet { #[pallet::storage] pub type MaxPools = StorageValue<_, u32, OptionQuery>; - /// Maximum number of delegators that can exist in the system. If `None`, then the count - /// delegators are not bound on a system wide basis. + /// Maximum number of members that can exist in the system. If `None`, then the count + /// members are not bound on a system wide basis. #[pallet::storage] - pub type MaxDelegators = StorageValue<_, u32, OptionQuery>; + pub type MaxPoolMembers = StorageValue<_, u32, OptionQuery>; - /// Maximum number of delegators that may belong to pool. If `None`, then the count of - /// delegators is not bound on a per pool basis. + /// Maximum number of members that may belong to pool. If `None`, then the count of + /// members is not bound on a per pool basis. #[pallet::storage] - pub type MaxDelegatorsPerPool = StorageValue<_, u32, OptionQuery>; + pub type MaxPoolMembersPerPool = StorageValue<_, u32, OptionQuery>; - /// Active delegators. + /// Active members. #[pallet::storage] - pub type Delegators = CountedStorageMap<_, Twox64Concat, T::AccountId, Delegator>; + pub type PoolMembers = CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember>; /// Storage for bonded pools. // To get or insert a pool see [`BondedPool::get`] and [`BondedPool::put`] @@ -1118,7 +1118,7 @@ pub mod pallet { pub type BondedPools = CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner>; - /// Reward pools. This is where there rewards for each pool accumulate. When a delegators payout + /// Reward pools. This is where there rewards for each pool accumulate. When a members payout /// is claimed, the balance comes out fo the reward pool. Keyed by the bonded pools account. #[pallet::storage] pub type RewardPools = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool>; @@ -1145,8 +1145,8 @@ pub mod pallet { pub min_join_bond: BalanceOf, pub min_create_bond: BalanceOf, pub max_pools: Option, - pub max_delegators_per_pool: Option, - pub max_delegators: Option, + pub max_members_per_pool: Option, + pub max_members: Option, } #[cfg(feature = "std")] @@ -1156,8 +1156,8 @@ pub mod pallet { min_join_bond: Zero::zero(), min_create_bond: Zero::zero(), max_pools: Some(16), - max_delegators_per_pool: Some(32), - max_delegators: Some(16 * 32), + max_members_per_pool: Some(32), + max_members: Some(16 * 32), } } } @@ -1170,11 +1170,11 @@ pub mod pallet { if let Some(max_pools) = self.max_pools { MaxPools::::put(max_pools); } - if let Some(max_delegators_per_pool) = self.max_delegators_per_pool { - MaxDelegatorsPerPool::::put(max_delegators_per_pool); + if let Some(max_members_per_pool) = self.max_members_per_pool { + MaxPoolMembersPerPool::::put(max_members_per_pool); } - if let Some(max_delegators) = self.max_delegators { - MaxDelegators::::put(max_delegators); + if let Some(max_members) = self.max_members { + MaxPoolMembers::::put(max_members); } } } @@ -1185,14 +1185,14 @@ pub mod pallet { pub enum Event { /// A pool has been created. Created { depositor: T::AccountId, pool_id: PoolId }, - /// A delegator has became bonded in a pool. - Bonded { delegator: T::AccountId, pool_id: PoolId, bonded: BalanceOf, joined: bool }, - /// A payout has been made to a delegator. - PaidOut { delegator: T::AccountId, pool_id: PoolId, payout: BalanceOf }, - /// A delegator has unbonded from their pool. - Unbonded { delegator: T::AccountId, pool_id: PoolId, amount: BalanceOf }, - /// A delegator has withdrawn from their pool. - Withdrawn { delegator: T::AccountId, pool_id: PoolId, amount: BalanceOf }, + /// A member has became bonded in a pool. + Bonded { member: T::AccountId, pool_id: PoolId, bonded: BalanceOf, joined: bool }, + /// A payout has been made to a member. + PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf }, + /// A member has unbonded from their pool. + Unbonded { member: T::AccountId, pool_id: PoolId, amount: BalanceOf }, + /// A member has withdrawn from their pool. + Withdrawn { member: T::AccountId, pool_id: PoolId, amount: BalanceOf }, /// A pool has been destroyed. Destroyed { pool_id: PoolId }, /// The state of a pool has changed @@ -1204,8 +1204,8 @@ pub mod pallet { pub enum Error { /// A (bonded) pool id does not exist. PoolNotFound, - /// An account is not a delegator. - DelegatorNotFound, + /// An account is not a member. + PoolMemberNotFound, /// A reward pool does not exist. In all cases this is a system logic error. RewardPoolNotFound, /// A sub pool does not exist. @@ -1215,12 +1215,12 @@ pub mod pallet { AccountBelongsToOtherPool, /// The pool has insufficient balance to bond as a nominator. InsufficientBond, - /// The delegator is already unbonding in this era. + /// The member is already unbonding in this era. AlreadyUnbonding, - /// The delegator is fully unbonded (and thus cannot access the bonded and reward pool + /// The member is fully unbonded (and thus cannot access the bonded and reward pool /// anymore to, for example, collect rewards). FullyUnbonding, - /// The delegator cannot unbond further chunks due to reaching the limit. + /// The member cannot unbond further chunks due to reaching the limit. MaxUnbondingLimit, /// None of the funds can be withdrawn yet because the bonding duration has not passed. CannotWithdrawAny, @@ -1229,11 +1229,11 @@ pub mod pallet { /// The transaction could not be executed due to overflow risk for the pool. OverflowRisk, /// A pool must be in [`PoolState::Destroying`] in order for the depositor to unbond or for - /// other delegators to be permissionlessly unbonded. + /// other members to be permissionlessly unbonded. NotDestroying, - /// The depositor must be the only delegator in the bonded pool in order to unbond. And the - /// depositor must be the only delegator in the sub pools in order to withdraw unbonded. - NotOnlyDelegator, + /// The depositor must be the only member in the bonded pool in order to unbond. And the + /// depositor must be the only member in the sub pools in order to withdraw unbonded. + NotOnlyPoolMember, /// The caller does not have nominating permissions for the pool. NotNominator, /// Either a) the caller cannot make a valid kick or b) the pool is not destroying. @@ -1242,8 +1242,8 @@ pub mod pallet { NotOpen, /// The system is maxed out on pools. MaxPools, - /// Too many delegators in the pool or system. - MaxDelegators, + /// Too many members in the pool or system. + MaxPoolMembers, /// The pools state cannot be changed. CanNotChangeState, /// The caller does not have adequate permissions. @@ -1259,14 +1259,14 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Stake funds with a pool. The amount to bond is transferred from the delegator to the + /// Stake funds with a pool. The amount to bond is transferred from the member to the /// pools account and immediately increases the pools bond. /// /// # Note /// /// * An account can only be a member of a single pool. /// * An account cannot join the same pool multiple times. - /// * This call will *not* dust the delegator account, so the delegator must have at least + /// * This call will *not* dust the member account, so the member must have at least /// `existential deposit + amount` in their account. /// * Only a pool with [`PoolState::Open`] can be joined #[pallet::weight(T::WeightInfo::join())] @@ -1279,8 +1279,8 @@ pub mod pallet { let who = ensure_signed(origin)?; ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); - // If a delegator already exists that means they already belong to a pool - ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); + // If a member already exists that means they already belong to a pool + ensure!(!PoolMembers::::contains_key(&who), Error::::AccountBelongsToOtherPool); let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; bonded_pool.ok_to_join(amount)?; @@ -1290,16 +1290,16 @@ pub mod pallet { let reward_pool = RewardPool::::get_and_update(pool_id) .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; - bonded_pool.try_inc_delegators()?; + bonded_pool.try_inc_members()?; let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Later)?; - Delegators::insert( + PoolMembers::insert( who.clone(), - Delegator:: { + PoolMember:: { pool_id, points: points_issued, // At best the reward pool has the rewards up through the previous era. If the - // delegator joins prior to the snapshot they will benefit from the rewards of + // member joins prior to the snapshot they will benefit from the rewards of // the active era despite not contributing to the pool's vote weight. If they // join after the snapshot is taken they will benefit from the rewards of the // next 2 eras because their vote weight will not be counted until the @@ -1310,7 +1310,7 @@ pub mod pallet { ); Self::deposit_event(Event::::Bonded { - delegator: who, + member: who, pool_id, bonded: amount, joined: true, @@ -1334,8 +1334,8 @@ pub mod pallet { #[transactional] pub fn bond_extra(origin: OriginFor, extra: BondExtra>) -> DispatchResult { let who = ensure_signed(origin)?; - let (mut delegator, mut bonded_pool, mut reward_pool) = - Self::get_delegator_with_pools(&who)?; + let (mut member, mut bonded_pool, mut reward_pool) = + Self::get_member_with_pools(&who)?; let (points_issued, bonded) = match extra { BondExtra::FreeBalance(amount) => @@ -1343,7 +1343,7 @@ pub mod pallet { BondExtra::Rewards => { let claimed = Self::do_reward_payout( &who, - &mut delegator, + &mut member, &mut bonded_pool, &mut reward_pool, )?; @@ -1351,40 +1351,40 @@ pub mod pallet { }, }; bonded_pool.ok_to_be_open(bonded)?; - delegator.points = delegator.points.saturating_add(points_issued); + member.points = member.points.saturating_add(points_issued); Self::deposit_event(Event::::Bonded { - delegator: who.clone(), - pool_id: delegator.pool_id, + member: who.clone(), + pool_id: member.pool_id, bonded, joined: false, }); - Self::put_delegator_with_pools(&who, delegator, bonded_pool, reward_pool); + Self::put_member_with_pools(&who, member, bonded_pool, reward_pool); Ok(()) } - /// A bonded delegator can use this to claim their payout based on the rewards that the pool + /// A bonded member can use this to claim their payout based on the rewards that the pool /// has accumulated since their last claimed payout (OR since joining if this is there first - /// time claiming rewards). The payout will be transferred to the delegator's account. + /// time claiming rewards). The payout will be transferred to the member's account. /// - /// The delegator will earn rewards pro rata based on the delegators stake vs the sum of the - /// delegators in the pools stake. Rewards do not "expire". + /// The member will earn rewards pro rata based on the members stake vs the sum of the + /// members in the pools stake. Rewards do not "expire". #[pallet::weight(T::WeightInfo::claim_payout())] #[transactional] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let (mut delegator, mut bonded_pool, mut reward_pool) = - Self::get_delegator_with_pools(&who)?; + let (mut member, mut bonded_pool, mut reward_pool) = + Self::get_member_with_pools(&who)?; let _ = - Self::do_reward_payout(&who, &mut delegator, &mut bonded_pool, &mut reward_pool)?; + Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; - Self::put_delegator_with_pools(&who, delegator, bonded_pool, reward_pool); + Self::put_member_with_pools(&who, member, bonded_pool, reward_pool); Ok(()) } - /// Unbond up to `unbonding_points` of the `delegator_account`'s funds from the pool. It + /// Unbond up to `unbonding_points` of the `member_account`'s funds from the pool. It /// implicitly collects the rewards one last time, since not doing so would mean some /// rewards would go forfeited. /// @@ -1395,15 +1395,15 @@ pub mod pallet { /// /// * The pool is blocked and the caller is either the root or state-toggler. This is /// refereed to as a kick. - /// * The pool is destroying and the delegator is not the depositor. - /// * The pool is destroying, the delegator is the depositor and no other delegators are in + /// * The pool is destroying and the member is not the depositor. + /// * The pool is destroying, the member is the depositor and no other members are in /// the pool. /// /// ## Conditions for permissioned dispatch (i.e. the caller is also the - /// `delegator_account`): + /// `member_account`): /// /// * The caller is not the depositor. - /// * The caller is the depositor, the pool is destroying and no other delegators are in the + /// * The caller is the depositor, the pool is destroying and no other members are in the /// pool. /// /// # Note @@ -1416,17 +1416,17 @@ pub mod pallet { #[transactional] pub fn unbond( origin: OriginFor, - delegator_account: T::AccountId, + member_account: T::AccountId, #[pallet::compact] unbonding_points: BalanceOf, ) -> DispatchResult { let caller = ensure_signed(origin)?; - let (mut delegator, mut bonded_pool, mut reward_pool) = - Self::get_delegator_with_pools(&delegator_account)?; + let (mut member, mut bonded_pool, mut reward_pool) = + Self::get_member_with_pools(&member_account)?; bonded_pool.ok_to_unbond_with( &caller, - &delegator_account, - &delegator, + &member_account, + &member, unbonding_points, )?; @@ -1434,8 +1434,8 @@ pub mod pallet { // no longer exist in the bonded pool and thus they can no longer claim their payouts. // It is not strictly necessary to claim the rewards, but we do it here for UX. Self::do_reward_payout( - &delegator_account, - &mut delegator, + &member_account, + &mut member, &mut bonded_pool, &mut reward_pool, )?; @@ -1443,15 +1443,15 @@ pub mod pallet { let current_era = T::StakingInterface::current_era(); let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); - // Try and unbond in the delegator map. - delegator.try_unbond(unbonding_points, unbond_era)?; + // Try and unbond in the member map. + member.try_unbond(unbonding_points, unbond_era)?; // Unbond in the actual underlying nominator. let unbonding_balance = bonded_pool.dissolve(unbonding_points); T::StakingInterface::unbond(bonded_pool.bonded_account(), unbonding_balance)?; // Note that we lazily create the unbonding pools here if they don't already exist - let mut sub_pools = SubPoolsStorage::::get(delegator.pool_id) + let mut sub_pools = SubPoolsStorage::::get(member.pool_id) .unwrap_or_default() .maybe_merge_pools(unbond_era); @@ -1474,14 +1474,14 @@ pub mod pallet { .issue(unbonding_balance); Self::deposit_event(Event::::Unbonded { - delegator: delegator_account.clone(), - pool_id: delegator.pool_id, + member: member_account.clone(), + pool_id: member.pool_id, amount: unbonding_balance, }); // Now that we know everything has worked write the items to storage. - SubPoolsStorage::insert(&delegator.pool_id, sub_pools); - Self::put_delegator_with_pools(&delegator_account, delegator, bonded_pool, reward_pool); + SubPoolsStorage::insert(&member.pool_id, sub_pools); + Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool); Ok(()) } @@ -1508,7 +1508,7 @@ pub mod pallet { Ok(()) } - /// Withdraw unbonded funds from `delegator_account`. If no bonded funds can be unbonded, an + /// Withdraw unbonded funds from `member_account`. If no bonded funds can be unbonded, an /// error is returned. /// /// Under certain conditions, this call can be dispatched permissionlessly (i.e. by any @@ -1517,7 +1517,7 @@ pub mod pallet { /// # Conditions for a permissionless dispatch /// /// * The pool is in destroy mode and the target is not the depositor. - /// * The target is the depositor and they are the only delegator in the sub pools. + /// * The target is the depositor and they are the only member in the sub pools. /// * The pool is blocked and the caller is either the root or state-toggler. /// /// # Conditions for permissioned dispatch @@ -1533,28 +1533,28 @@ pub mod pallet { #[transactional] pub fn withdraw_unbonded( origin: OriginFor, - delegator_account: T::AccountId, + member_account: T::AccountId, num_slashing_spans: u32, ) -> DispatchResultWithPostInfo { let caller = ensure_signed(origin)?; - let mut delegator = - Delegators::::get(&delegator_account).ok_or(Error::::DelegatorNotFound)?; + let mut member = + PoolMembers::::get(&member_account).ok_or(Error::::PoolMemberNotFound)?; let current_era = T::StakingInterface::current_era(); - let bonded_pool = BondedPool::::get(delegator.pool_id) + let bonded_pool = BondedPool::::get(member.pool_id) .defensive_ok_or_else(|| Error::::PoolNotFound)?; - let mut sub_pools = SubPoolsStorage::::get(delegator.pool_id) + let mut sub_pools = SubPoolsStorage::::get(member.pool_id) .defensive_ok_or_else(|| Error::::SubPoolsNotFound)?; bonded_pool.ok_to_withdraw_unbonded_with( &caller, - &delegator_account, - &delegator, + &member_account, + &member, &sub_pools, )?; // NOTE: must do this after we have done the `ok_to_withdraw_unbonded_other_with` check. - let withdrawn_points = delegator.withdraw_unlocked(current_era); + let withdrawn_points = member.withdraw_unlocked(current_era); ensure!(!withdrawn_points.is_empty(), Error::::CannotWithdrawAny); // Before calculate the `balance_to_unbond`, with call withdraw unbonded to ensure the @@ -1580,44 +1580,44 @@ pub mod pallet { } }) // A call to this function may cause the pool's stash to get dusted. If this happens - // before the last delegator has withdrawn, then all subsequent withdraws will be 0. + // before the last member has withdrawn, then all subsequent withdraws will be 0. // However the unbond pools do no get updated to reflect this. In the aforementioned // scenario, this check ensures we don't try to withdraw funds that don't exist. // This check is also defensive in cases where the unbond pool does not update its // balance (e.g. a bug in the slashing hook.) We gracefully proceed in order to - // ensure delegators can leave the pool and it can be destroyed. + // ensure members can leave the pool and it can be destroyed. .min(bonded_pool.transferrable_balance()); T::Currency::transfer( &bonded_pool.bonded_account(), - &delegator_account, + &member_account, balance_to_unbond, ExistenceRequirement::AllowDeath, ) .defensive()?; Self::deposit_event(Event::::Withdrawn { - delegator: delegator_account.clone(), - pool_id: delegator.pool_id, + member: member_account.clone(), + pool_id: member.pool_id, amount: balance_to_unbond, }); - let post_info_weight = if delegator.active_points().is_zero() { - // delegator being reaped. - Delegators::::remove(&delegator_account); + let post_info_weight = if member.active_points().is_zero() { + // member being reaped. + PoolMembers::::remove(&member_account); - if delegator_account == bonded_pool.roles.depositor { + if member_account == bonded_pool.roles.depositor { Pallet::::dissolve_pool(bonded_pool); None } else { - bonded_pool.dec_delegators().put(); - SubPoolsStorage::::insert(&delegator.pool_id, sub_pools); + bonded_pool.dec_members().put(); + SubPoolsStorage::::insert(&member.pool_id, sub_pools); Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) } } else { // we certainly don't need to delete any pools, because no one is being removed. - SubPoolsStorage::::insert(&delegator.pool_id, sub_pools); - Delegators::::insert(&delegator_account, delegator); + SubPoolsStorage::::insert(&member.pool_id, sub_pools); + PoolMembers::::insert(&member_account, member); Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) }; @@ -1664,7 +1664,7 @@ pub mod pallet { .map_or(true, |max_pools| BondedPools::::count() < max_pools), Error::::MaxPools ); - ensure!(!Delegators::::contains_key(&who), Error::::AccountBelongsToOtherPool); + ensure!(!PoolMembers::::contains_key(&who), Error::::AccountBelongsToOtherPool); let pool_id = LastPoolId::::mutate(|id| { *id += 1; @@ -1675,7 +1675,7 @@ pub mod pallet { PoolRoles { root, nominator, state_toggler, depositor: who.clone() }, ); - bonded_pool.try_inc_delegators()?; + bonded_pool.try_inc_members()?; let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?; T::Currency::transfer( @@ -1685,9 +1685,9 @@ pub mod pallet { ExistenceRequirement::AllowDeath, )?; - Delegators::::insert( + PoolMembers::::insert( who.clone(), - Delegator:: { + PoolMember:: { pool_id, points, reward_pool_total_earnings: Zero::zero(), @@ -1708,7 +1708,7 @@ pub mod pallet { pool_id: pool_id.clone(), }); Self::deposit_event(Event::::Bonded { - delegator: who, + member: who, pool_id, bonded: amount, joined: true, @@ -1786,16 +1786,16 @@ pub mod pallet { /// * `min_join_bond` - Set [`MinJoinBond`]. /// * `min_create_bond` - Set [`MinCreateBond`]. /// * `max_pools` - Set [`MaxPools`]. - /// * `max_delegators` - Set [`MaxDelegators`]. - /// * `max_delegators_per_pool` - Set [`MaxDelegatorsPerPool`]. + /// * `max_members` - Set [`MaxPoolMembers`]. + /// * `max_members_per_pool` - Set [`MaxPoolMembersPerPool`]. #[pallet::weight(T::WeightInfo::set_configs())] pub fn set_configs( origin: OriginFor, min_join_bond: ConfigOp>, min_create_bond: ConfigOp>, max_pools: ConfigOp, - max_delegators: ConfigOp, - max_delegators_per_pool: ConfigOp, + max_members: ConfigOp, + max_members_per_pool: ConfigOp, ) -> DispatchResult { ensure_root(origin)?; @@ -1812,8 +1812,8 @@ pub mod pallet { config_op_exp!(MinJoinBond::, min_join_bond); config_op_exp!(MinCreateBond::, min_create_bond); config_op_exp!(MaxPools::, max_pools); - config_op_exp!(MaxDelegators::, max_delegators); - config_op_exp!(MaxDelegatorsPerPool::, max_delegators_per_pool); + config_op_exp!(MaxPoolMembers::, max_members); + config_op_exp!(MaxPoolMembersPerPool::, max_members_per_pool); Ok(()) } @@ -1894,29 +1894,29 @@ impl Pallet { T::PalletId::get().into_sub_account((AccountType::Reward, id)) } - /// Get the delegator with their associated bonded and reward pool. - fn get_delegator_with_pools( + /// Get the member with their associated bonded and reward pool. + fn get_member_with_pools( who: &T::AccountId, - ) -> Result<(Delegator, BondedPool, RewardPool), Error> { - let delegator = Delegators::::get(&who).ok_or(Error::::DelegatorNotFound)?; + ) -> Result<(PoolMember, BondedPool, RewardPool), Error> { + let member = PoolMembers::::get(&who).ok_or(Error::::PoolMemberNotFound)?; let bonded_pool = - BondedPool::::get(delegator.pool_id).defensive_ok_or(Error::::PoolNotFound)?; + BondedPool::::get(member.pool_id).defensive_ok_or(Error::::PoolNotFound)?; let reward_pool = - RewardPools::::get(delegator.pool_id).defensive_ok_or(Error::::PoolNotFound)?; - Ok((delegator, bonded_pool, reward_pool)) + RewardPools::::get(member.pool_id).defensive_ok_or(Error::::PoolNotFound)?; + Ok((member, bonded_pool, reward_pool)) } - /// Persist the delegator with their associated bonded and reward pool into storage, consuming + /// Persist the member with their associated bonded and reward pool into storage, consuming /// all of them. - fn put_delegator_with_pools( - delegator_account: &T::AccountId, - delegator: Delegator, + fn put_member_with_pools( + member_account: &T::AccountId, + member: PoolMember, bonded_pool: BondedPool, reward_pool: RewardPool, ) { bonded_pool.put(); - RewardPools::insert(delegator.pool_id, reward_pool); - Delegators::::insert(delegator_account, delegator); + RewardPools::insert(member.pool_id, reward_pool); + PoolMembers::::insert(member_account, member); } /// Calculate the equivalent point of `new_funds` in a pool with `current_balance` and @@ -1967,11 +1967,11 @@ impl Pallet { .div(current_points) } - /// Calculate the rewards for `delegator`. + /// Calculate the rewards for `member`. /// /// Returns the payout amount. - fn calculate_delegator_payout( - delegator: &mut Delegator, + fn calculate_member_payout( + member: &mut PoolMember, bonded_pool: &mut BondedPool, reward_pool: &mut RewardPool, ) -> Result, DispatchError> { @@ -1987,34 +1987,34 @@ impl Pallet { // The new points that will be added to the pool. For every unit of balance that has been // earned by the reward pool, we inflate the reward pool points by `bonded_pool.points`. In // effect this allows each, single unit of balance (e.g. plank) to be divvied up pro rata - // among delegators based on points. + // among members based on points. let new_points = u256(bonded_pool.points).saturating_mul(new_earnings); // The points of the reward pool after taking into account the new earnings. Notice that - // this only stays even or increases over time except for when we subtract delegator virtual + // this only stays even or increases over time except for when we subtract member virtual // shares. let current_points = bonded_pool.bound_check(reward_pool.points.saturating_add(new_points)); - // The rewards pool's earnings since the last time this delegator claimed a payout. + // The rewards pool's earnings since the last time this member claimed a payout. let new_earnings_since_last_claim = - reward_pool.total_earnings.saturating_sub(delegator.reward_pool_total_earnings); + reward_pool.total_earnings.saturating_sub(member.reward_pool_total_earnings); - // The points of the reward pool that belong to the delegator. - let delegator_virtual_points = - // The delegators portion of the reward pool - u256(delegator.active_points()) - // times the amount the pool has earned since the delegator last claimed. + // The points of the reward pool that belong to the member. + let member_virtual_points = + // The members portion of the reward pool + u256(member.active_points()) + // times the amount the pool has earned since the member last claimed. .saturating_mul(u256(new_earnings_since_last_claim)); - let delegator_payout = if delegator_virtual_points.is_zero() || + let member_payout = if member_virtual_points.is_zero() || current_points.is_zero() || reward_pool.balance.is_zero() { Zero::zero() } else { - // Equivalent to `(delegator_virtual_points / current_points) * reward_pool.balance` + // Equivalent to `(member_virtual_points / current_points) * reward_pool.balance` let numerator = { - let numerator = delegator_virtual_points.saturating_mul(u256(reward_pool.balance)); + let numerator = member_virtual_points.saturating_mul(u256(reward_pool.balance)); bonded_pool.bound_check(numerator) }; balance( @@ -2028,52 +2028,52 @@ impl Pallet { if reward_pool.total_earnings == BalanceOf::::max_value() { bonded_pool.set_state(PoolState::Destroying); }; - delegator.reward_pool_total_earnings = reward_pool.total_earnings; - reward_pool.points = current_points.saturating_sub(delegator_virtual_points); - reward_pool.balance = reward_pool.balance.saturating_sub(delegator_payout); + member.reward_pool_total_earnings = reward_pool.total_earnings; + reward_pool.points = current_points.saturating_sub(member_virtual_points); + reward_pool.balance = reward_pool.balance.saturating_sub(member_payout); - Ok(delegator_payout) + Ok(member_payout) } - /// If the delegator has some rewards, transfer a payout from the reward pool to the delegator. + /// If the member has some rewards, transfer a payout from the reward pool to the member. // Emits events and potentially modifies pool state if any arithmetic saturates, but does // not persist any of the mutable inputs to storage. fn do_reward_payout( - delegator_account: &T::AccountId, - delegator: &mut Delegator, + member_account: &T::AccountId, + member: &mut PoolMember, bonded_pool: &mut BondedPool, reward_pool: &mut RewardPool, ) -> Result, DispatchError> { - debug_assert_eq!(delegator.pool_id, bonded_pool.id); - // a delegator who has no skin in the game anymore cannot claim any rewards. - ensure!(!delegator.active_points().is_zero(), Error::::FullyUnbonding); + debug_assert_eq!(member.pool_id, bonded_pool.id); + // a member who has no skin in the game anymore cannot claim any rewards. + ensure!(!member.active_points().is_zero(), Error::::FullyUnbonding); let was_destroying = bonded_pool.is_destroying(); - let delegator_payout = - Self::calculate_delegator_payout(delegator, bonded_pool, reward_pool)?; + let member_payout = + Self::calculate_member_payout(member, bonded_pool, reward_pool)?; - // Transfer payout to the delegator. + // Transfer payout to the member. T::Currency::transfer( &bonded_pool.reward_account(), - &delegator_account, - delegator_payout, + &member_account, + member_payout, ExistenceRequirement::AllowDeath, )?; Self::deposit_event(Event::::PaidOut { - delegator: delegator_account.clone(), - pool_id: delegator.pool_id, - payout: delegator_payout, + member: member_account.clone(), + pool_id: member.pool_id, + payout: member_payout, }); if bonded_pool.is_destroying() && !was_destroying { Self::deposit_event(Event::::StateChanged { - pool_id: delegator.pool_id, + pool_id: member.pool_id, new_state: PoolState::Destroying, }); } - Ok(delegator_payout) + Ok(member_payout) } /// Ensure the correctness of the state of this pallet. @@ -2089,16 +2089,16 @@ impl Pallet { /// * `Metadata` keys must be a subset of the above superset. /// * the count of the above set must be less than `MaxPools`. /// - /// Then, considering delegators as well: + /// Then, considering members as well: /// - /// * each `BondedPool.delegator_counter` must be: - /// - correct (compared to actual count of delegator who have `.pool_id` this pool) - /// - less than `MaxDelegatorsPerPool`. - /// * each `delegator.pool_id` must correspond to an existing `BondedPool.id` (which implies the + /// * each `BondedPool.member_counter` must be: + /// - correct (compared to actual count of member who have `.pool_id` this pool) + /// - less than `MaxPoolMembersPerPool`. + /// * each `member.pool_id` must correspond to an existing `BondedPool.id` (which implies the /// existence of the reward pool as well). - /// * count of all delegators must be less than `MaxDelegators`. + /// * count of all members must be less than `MaxPoolMembers`. /// - /// Then, considering unbonding delegators: + /// Then, considering unbonding members: /// /// for each pool: /// * sum of the balance that's tracked in all unbonding pools must be the same as the @@ -2132,25 +2132,25 @@ impl Pallet { assert!(T::Currency::free_balance(&account) >= T::Currency::minimum_balance()); } - let mut pools_delegators = BTreeMap::::new(); - let mut all_delegators = 0u32; - Delegators::::iter().for_each(|(_, d)| { + let mut pools_members = BTreeMap::::new(); + let mut all_members = 0u32; + PoolMembers::::iter().for_each(|(_, d)| { assert!(BondedPools::::contains_key(d.pool_id)); - assert!(!d.total_points().is_zero(), "no delegator should have zero points: {:?}", d); - *pools_delegators.entry(d.pool_id).or_default() += 1; - all_delegators += 1; + assert!(!d.total_points().is_zero(), "no member should have zero points: {:?}", d); + *pools_members.entry(d.pool_id).or_default() += 1; + all_members += 1; }); BondedPools::::iter().for_each(|(id, inner)| { let bonded_pool = BondedPool { id, inner }; assert_eq!( - pools_delegators.get(&id).map(|x| *x).unwrap_or_default(), - bonded_pool.delegator_counter + pools_members.get(&id).map(|x| *x).unwrap_or_default(), + bonded_pool.member_counter ); - assert!(MaxDelegatorsPerPool::::get() - .map_or(true, |max| bonded_pool.delegator_counter <= max)); + assert!(MaxPoolMembersPerPool::::get() + .map_or(true, |max| bonded_pool.member_counter <= max)); - let depositor = Delegators::::get(&bonded_pool.roles.depositor).unwrap(); + let depositor = PoolMembers::::get(&bonded_pool.roles.depositor).unwrap(); assert!( bonded_pool.is_destroying_and_only_depositor(depositor.active_points()) || depositor.active_points() >= MinCreateBond::::get(), @@ -2158,7 +2158,7 @@ impl Pallet { pool is being destroyed and the depositor is the last member", ); }); - assert!(MaxDelegators::::get().map_or(true, |max| all_delegators <= max)); + assert!(MaxPoolMembers::::get().map_or(true, |max| all_members <= max)); if level <= 1 { return Ok(()) @@ -2187,18 +2187,18 @@ impl Pallet { Ok(()) } - /// Fully unbond the shares of `delegator`, when executed from `origin`. + /// Fully unbond the shares of `member`, when executed from `origin`. /// /// This is useful for backwards compatibility with the majority of tests that only deal with /// full unbonding, not partial unbonding. #[cfg(any(feature = "runtime-benchmarks", test))] pub fn fully_unbond( origin: frame_system::pallet_prelude::OriginFor, - delegator: T::AccountId, + member: T::AccountId, ) -> DispatchResult { let points = - Delegators::::get(&delegator).map(|d| d.active_points()).unwrap_or_default(); - Self::unbond(origin, delegator, points) + PoolMembers::::get(&member).map(|d| d.active_points()).unwrap_or_default(); + Self::unbond(origin, member, points) } } diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index a84c45398ff72..5498496965adb 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -192,13 +192,13 @@ frame_support::construct_runtime!( #[derive(Default)] pub struct ExtBuilder { - delegators: Vec<(AccountId, Balance)>, + members: Vec<(AccountId, Balance)>, } impl ExtBuilder { - // Add delegators to pool 0. - pub(crate) fn add_delegators(mut self, delegators: Vec<(AccountId, Balance)>) -> Self { - self.delegators = delegators; + // Add members to pool 0. + pub(crate) fn add_members(mut self, members: Vec<(AccountId, Balance)>) -> Self { + self.members = members; self } @@ -220,8 +220,8 @@ impl ExtBuilder { min_join_bond: 2, min_create_bond: 2, max_pools: Some(2), - max_delegators_per_pool: Some(3), - max_delegators: Some(4), + max_members_per_pool: Some(3), + max_members: Some(4), } .assimilate_storage(&mut storage); @@ -237,7 +237,7 @@ impl ExtBuilder { 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.delegators { + 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)); } @@ -279,11 +279,11 @@ pub(crate) fn pool_events_since_last_call() -> Vec> { } /// Same as `fully_unbond`, in permissioned setting. -pub fn fully_unbond_permissioned(delegator: AccountId) -> DispatchResult { - let points = Delegators::::get(&delegator) +pub fn fully_unbond_permissioned(member: AccountId) -> DispatchResult { + let points = PoolMembers::::get(&member) .map(|d| d.active_points()) .unwrap_or_default(); - Pools::unbond(Origin::signed(delegator), delegator, points) + Pools::unbond(Origin::signed(member), member, points) } #[cfg(test)] diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 15beb0572b9e1..3855e2372f030 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -30,7 +30,7 @@ macro_rules! unbonding_pools_with_era { }}; } -macro_rules! delegator_unbonding_eras { +macro_rules! member_unbonding_eras { ($( $any:tt )*) => {{ let x: BoundedBTreeMap = bounded_btree_map!($( $any )*); x @@ -46,7 +46,7 @@ fn test_setup_works() { assert_eq!(BondedPools::::count(), 1); assert_eq!(RewardPools::::count(), 1); assert_eq!(SubPoolsStorage::::count(), 0); - assert_eq!(Delegators::::count(), 1); + assert_eq!(PoolMembers::::count(), 1); assert_eq!(StakingMock::bonding_duration(), 3); let last_pool = LastPoolId::::get(); @@ -57,7 +57,7 @@ fn test_setup_works() { inner: BondedPoolInner { state: PoolState::Open, points: 10, - delegator_counter: 1, + member_counter: 1, roles: DEFAULT_ROLES }, } @@ -67,8 +67,8 @@ fn test_setup_works() { RewardPool:: { balance: 0, points: 0.into(), total_earnings: 0 } ); assert_eq!( - Delegators::::get(10).unwrap(), - Delegator:: { pool_id: last_pool, points: 10, ..Default::default() } + PoolMembers::::get(10).unwrap(), + PoolMember:: { pool_id: last_pool, points: 10, ..Default::default() } ) }) } @@ -82,7 +82,7 @@ mod bonded_pool { inner: BondedPoolInner { state: PoolState::Open, points: 100, - delegator_counter: 1, + member_counter: 1, roles: DEFAULT_ROLES, }, }; @@ -134,7 +134,7 @@ mod bonded_pool { inner: BondedPoolInner { state: PoolState::Open, points: 100, - delegator_counter: 1, + member_counter: 1, roles: DEFAULT_ROLES, }, }; @@ -181,7 +181,7 @@ mod bonded_pool { inner: BondedPoolInner { state: PoolState::Open, points: 100, - delegator_counter: 1, + member_counter: 1, roles: DEFAULT_ROLES, }, }; @@ -390,27 +390,27 @@ mod join { #[test] fn join_works() { - let bonded = |points, delegator_counter| BondedPool:: { + let bonded = |points, member_counter| BondedPool:: { id: 1, inner: BondedPoolInner { state: PoolState::Open, points, - delegator_counter, + member_counter, roles: DEFAULT_ROLES, }, }; ExtBuilder::default().build_and_execute(|| { // Given Balances::make_free_balance_be(&11, ExistentialDeposit::get() + 2); - assert!(!Delegators::::contains_key(&11)); + assert!(!PoolMembers::::contains_key(&11)); // When assert_ok!(Pools::join(Origin::signed(11), 2, 1)); // then assert_eq!( - Delegators::::get(&11).unwrap(), - Delegator:: { pool_id: 1, points: 2, ..Default::default() } + PoolMembers::::get(&11).unwrap(), + PoolMember:: { pool_id: 1, points: 2, ..Default::default() } ); assert_eq!(BondedPool::::get(1).unwrap(), bonded(12, 2)); @@ -420,15 +420,15 @@ mod join { // And Balances::make_free_balance_be(&12, ExistentialDeposit::get() + 12); - assert!(!Delegators::::contains_key(&12)); + assert!(!PoolMembers::::contains_key(&12)); // When assert_ok!(Pools::join(Origin::signed(12), 12, 1)); // Then assert_eq!( - Delegators::::get(&12).unwrap(), - Delegator:: { pool_id: 1, points: 24, ..Default::default() } + PoolMembers::::get(&12).unwrap(), + PoolMember:: { pool_id: 1, points: 24, ..Default::default() } ); assert_eq!(BondedPool::::get(1).unwrap(), bonded(12 + 24, 3)); }); @@ -438,7 +438,7 @@ mod join { fn join_errors_correctly() { ExtBuilder::default().with_check(0).build_and_execute(|| { // 10 is already part of the default pool created. - assert_eq!(Delegators::::get(&10).unwrap().pool_id, 1); + assert_eq!(PoolMembers::::get(&10).unwrap().pool_id, 1); assert_noop!( Pools::join(Origin::signed(10), 420, 123), @@ -455,7 +455,7 @@ mod join { BondedPool:: { id: 123, inner: BondedPoolInner { - delegator_counter: 1, + member_counter: 1, state: PoolState::Open, points: 100, roles: DEFAULT_ROLES, @@ -511,7 +511,7 @@ mod join { inner: BondedPoolInner { state: PoolState::Open, points: 100, - delegator_counter: 1, + member_counter: 1, roles: DEFAULT_ROLES, }, } @@ -521,10 +521,10 @@ mod join { } #[test] - fn join_max_delegator_limits_are_respected() { + fn join_max_member_limits_are_respected() { ExtBuilder::default().build_and_execute(|| { // Given - assert_eq!(MaxDelegatorsPerPool::::get(), Some(3)); + assert_eq!(MaxPoolMembersPerPool::::get(), Some(3)); for i in 1..3 { let account = i + 100; Balances::make_free_balance_be(&account, 100 + Balances::minimum_balance()); @@ -535,11 +535,11 @@ mod join { Balances::make_free_balance_be(&103, 100 + Balances::minimum_balance()); // Then - assert_noop!(Pools::join(Origin::signed(103), 100, 1), Error::::MaxDelegators); + assert_noop!(Pools::join(Origin::signed(103), 100, 1), Error::::MaxPoolMembers); // Given - assert_eq!(Delegators::::count(), 3); - assert_eq!(MaxDelegators::::get(), Some(4)); + assert_eq!(PoolMembers::::count(), 3); + assert_eq!(MaxPoolMembers::::get(), Some(4)); Balances::make_free_balance_be(&104, 100 + Balances::minimum_balance()); assert_ok!(Pools::create(Origin::signed(104), 100, 104, 104, 104)); @@ -552,7 +552,7 @@ mod join { // Then assert_noop!( Pools::join(Origin::signed(103), 100, pool_account), - Error::::MaxDelegators + Error::::MaxPoolMembers ); }); } @@ -561,8 +561,8 @@ mod join { mod claim_payout { use super::*; - fn del(points: Balance, reward_pool_total_earnings: Balance) -> Delegator { - Delegator { + fn del(points: Balance, reward_pool_total_earnings: Balance) -> PoolMember { + PoolMember { pool_id: 1, points, reward_pool_total_earnings, @@ -577,9 +577,9 @@ mod claim_payout { #[test] fn claim_payout_works() { ExtBuilder::default() - .add_delegators(vec![(40, 40), (50, 50)]) + .add_members(vec![(40, 40), (50, 50)]) .build_and_execute(|| { - // Given each delegator currently has a free balance of + // Given each member currently has a free balance of Balances::make_free_balance_be(&10, 0); Balances::make_free_balance_be(&40, 0); Balances::make_free_balance_be(&50, 0); @@ -593,7 +593,7 @@ mod claim_payout { // Then // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool // balance - assert_eq!(Delegators::::get(10).unwrap(), del(10, 100)); + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 100)); assert_eq!( RewardPools::::get(&1).unwrap(), rew(90, 100 * 100 - 100 * 10, 100) @@ -606,7 +606,7 @@ mod claim_payout { // Then // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance - assert_eq!(Delegators::::get(40).unwrap(), del(40, 100)); + assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 100)); assert_eq!( RewardPools::::get(&1).unwrap(), rew(50, 9_000 - 100 * 40, 100) @@ -619,7 +619,7 @@ mod claim_payout { // Then // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance - assert_eq!(Delegators::::get(50).unwrap(), del(50, 100)); + assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 100)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 100)); assert_eq!(Balances::free_balance(&50), 50); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); @@ -632,7 +632,7 @@ mod claim_payout { // Then // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance - assert_eq!(Delegators::::get(10).unwrap(), del(10, 150)); + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 150)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(45, 5_000 - 50 * 10, 150)); assert_eq!(Balances::free_balance(&10), 10 + 5); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 45); @@ -643,7 +643,7 @@ mod claim_payout { // Then // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool // balance - assert_eq!(Delegators::::get(40).unwrap(), del(40, 150)); + assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 150)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(25, 4_500 - 50 * 40, 150)); assert_eq!(Balances::free_balance(&40), 40 + 20); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); @@ -658,7 +658,7 @@ mod claim_payout { // Then // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 // pool balance - assert_eq!(Delegators::::get(50).unwrap(), del(50, 200)); + assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 200)); assert_eq!( RewardPools::::get(&1).unwrap(), rew( @@ -666,7 +666,7 @@ mod claim_payout { // old pool points + points from new earnings - del points. // // points from new earnings = new earnings(50) * bonded_pool.points(100) - // del points = delegator.points(50) * new_earnings_since_last_claim (100) + // del points = member.points(50) * new_earnings_since_last_claim (100) (2_500 + 50 * 100) - 50 * 100, 200, ) @@ -679,7 +679,7 @@ mod claim_payout { // Then // We expect a payout of 5 - assert_eq!(Delegators::::get(10).unwrap(), del(10, 200)); + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 200)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(20, 2_500 - 10 * 50, 200)); assert_eq!(Balances::free_balance(&10), 15 + 5); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 20); @@ -693,7 +693,7 @@ mod claim_payout { // Then // We expect a payout of 40 - assert_eq!(Delegators::::get(10).unwrap(), del(10, 600)); + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 600)); assert_eq!( RewardPools::::get(&1).unwrap(), rew( @@ -701,7 +701,7 @@ mod claim_payout { // old pool points + points from new earnings - del points // // points from new earnings = new earnings(400) * bonded_pool.points(100) - // del points = delegator.points(10) * new_earnings_since_last_claim(400) + // del points = member.points(10) * new_earnings_since_last_claim(400) (2_000 + 400 * 100) - 10 * 400, 600 ) @@ -719,7 +719,7 @@ mod claim_payout { // Then // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool // balance - assert_eq!(Delegators::::get(10).unwrap(), del(10, 620)); + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 620)); assert_eq!( RewardPools::::get(&1).unwrap(), rew(398, (38_000 + 20 * 100) - 10 * 20, 620) @@ -733,7 +733,7 @@ mod claim_payout { // Then // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 // pool balance - assert_eq!(Delegators::::get(40).unwrap(), del(40, 620)); + assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 620)); assert_eq!( RewardPools::::get(&1).unwrap(), rew(210, 39_800 - 40 * 470, 620) @@ -746,7 +746,7 @@ mod claim_payout { // Then // Expect payout of 210: (21,000 / 21,000) * 210 - assert_eq!(Delegators::::get(50).unwrap(), del(50, 620)); + assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 620)); assert_eq!( RewardPools::::get(&1).unwrap(), rew(0, 21_000 - 50 * 420, 620) @@ -762,7 +762,7 @@ mod claim_payout { let _ = with_transaction(|| -> TransactionOutcome { let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); - let mut delegator = Delegators::::get(10).unwrap(); + let mut member = PoolMembers::::get(10).unwrap(); // -- reward_pool.total_earnings saturates @@ -772,7 +772,7 @@ mod claim_payout { // When assert_ok!(Pools::do_reward_payout( &10, - &mut delegator, + &mut member, &mut bonded_pool, &mut reward_pool )); @@ -788,7 +788,7 @@ mod claim_payout { // Given let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); - let mut delegator = Delegators::::get(10).unwrap(); + let mut member = PoolMembers::::get(10).unwrap(); // Force new_earnings * bonded_pool.points == 100 Balances::make_free_balance_be(&default_reward_account(), 5 + 10); assert_eq!(bonded_pool.points, 10); @@ -799,7 +799,7 @@ mod claim_payout { // When assert_ok!(Pools::do_reward_payout( &10, - &mut delegator, + &mut member, &mut bonded_pool, &mut reward_pool )); @@ -813,36 +813,36 @@ mod claim_payout { } #[test] - fn reward_payout_errors_if_a_delegator_is_fully_unbonding() { - ExtBuilder::default().add_delegators(vec![(11, 11)]).build_and_execute(|| { - // fully unbond the delegator. + fn reward_payout_errors_if_a_member_is_fully_unbonding() { + ExtBuilder::default().add_members(vec![(11, 11)]).build_and_execute(|| { + // fully unbond the member. assert_ok!(Pools::fully_unbond(Origin::signed(11), 11)); let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); - let mut delegator = Delegators::::get(11).unwrap(); + let mut member = PoolMembers::::get(11).unwrap(); assert_noop!( - Pools::do_reward_payout(&11, &mut delegator, &mut bonded_pool, &mut reward_pool,), + Pools::do_reward_payout(&11, &mut member, &mut bonded_pool, &mut reward_pool,), Error::::FullyUnbonding ); }); } #[test] - fn calculate_delegator_payout_works_with_a_pool_of_1() { + fn calculate_member_payout_works_with_a_pool_of_1() { let del = |reward_pool_total_earnings| del(10, reward_pool_total_earnings); ExtBuilder::default().build_and_execute(|| { let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); - let mut delegator = Delegators::::get(10).unwrap(); + let mut member = PoolMembers::::get(10).unwrap(); let ed = Balances::minimum_balance(); // Given no rewards have been earned // When - let payout = Pools::calculate_delegator_payout( - &mut delegator, + let payout = Pools::calculate_member_payout( + &mut member, &mut bonded_pool, &mut reward_pool, ) @@ -850,15 +850,15 @@ mod claim_payout { // Then assert_eq!(payout, 0); - assert_eq!(delegator, del(0)); + assert_eq!(member, del(0)); assert_eq!(reward_pool, rew(0, 0, 0)); // Given the pool has earned some rewards for the first time Balances::make_free_balance_be(&default_reward_account(), ed + 5); // When - let payout = Pools::calculate_delegator_payout( - &mut delegator, + let payout = Pools::calculate_member_payout( + &mut member, &mut bonded_pool, &mut reward_pool, ) @@ -867,14 +867,14 @@ mod claim_payout { // Then assert_eq!(payout, 5); // (10 * 5 del virtual points / 10 * 5 pool points) * 5 pool balance assert_eq!(reward_pool, rew(0, 0, 5)); - assert_eq!(delegator, del(5)); + assert_eq!(member, del(5)); // Given the pool has earned rewards again Balances::make_free_balance_be(&default_reward_account(), ed + 10); // When - let payout = Pools::calculate_delegator_payout( - &mut delegator, + let payout = Pools::calculate_member_payout( + &mut member, &mut bonded_pool, &mut reward_pool, ) @@ -883,14 +883,14 @@ mod claim_payout { // Then assert_eq!(payout, 10); // (10 * 10 del virtual points / 10 pool points) * 5 pool balance assert_eq!(reward_pool, rew(0, 0, 15)); - assert_eq!(delegator, del(15)); + assert_eq!(member, del(15)); // Given the pool has earned no new rewards Balances::make_free_balance_be(&default_reward_account(), ed + 0); // When - let payout = Pools::calculate_delegator_payout( - &mut delegator, + let payout = Pools::calculate_member_payout( + &mut member, &mut bonded_pool, &mut reward_pool, ) @@ -899,33 +899,33 @@ mod claim_payout { // Then assert_eq!(payout, 0); assert_eq!(reward_pool, rew(0, 0, 15)); - assert_eq!(delegator, del(15)); + assert_eq!(member, del(15)); }); } #[test] - fn calculate_delegator_payout_works_with_a_pool_of_3() { + fn calculate_member_payout_works_with_a_pool_of_3() { ExtBuilder::default() - .add_delegators(vec![(40, 40), (50, 50)]) + .add_members(vec![(40, 40), (50, 50)]) .build_and_execute(|| { let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); let ed = Balances::minimum_balance(); - // Delegator with 10 points - let mut del_10 = Delegators::::get(10).unwrap(); - // Delegator with 40 points - let mut del_40 = Delegators::::get(40).unwrap(); - // Delegator with 50 points - let mut del_50 = Delegators::::get(50).unwrap(); - - // Given we have a total of 100 points split among the delegators + // PoolMember with 10 points + let mut del_10 = PoolMembers::::get(10).unwrap(); + // PoolMember with 40 points + let mut del_40 = PoolMembers::::get(40).unwrap(); + // PoolMember with 50 points + let mut del_50 = PoolMembers::::get(50).unwrap(); + + // Given we have a total of 100 points split among the members assert_eq!(del_50.points + del_40.points + del_10.points, 100); assert_eq!(bonded_pool.points, 100); // and the reward pool has earned 100 in rewards Balances::make_free_balance_be(&default_reward_account(), ed + 100); // When - let payout = Pools::calculate_delegator_payout( + let payout = Pools::calculate_member_payout( &mut del_10, &mut bonded_pool, &mut reward_pool, @@ -940,7 +940,7 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 10)); // When - let payout = Pools::calculate_delegator_payout( + let payout = Pools::calculate_member_payout( &mut del_40, &mut bonded_pool, &mut reward_pool, @@ -954,7 +954,7 @@ mod claim_payout { reward_pool, rew( 50, - // old pool points - delegator virtual points + // old pool points - member virtual points 9_000 - 100 * 40, 100 ) @@ -963,7 +963,7 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 40)); // When - let payout = Pools::calculate_delegator_payout( + let payout = Pools::calculate_member_payout( &mut del_50, &mut bonded_pool, &mut reward_pool, @@ -981,7 +981,7 @@ mod claim_payout { Balances::make_free_balance_be(&default_reward_account(), ed + 50); // When - let payout = Pools::calculate_delegator_payout( + let payout = Pools::calculate_member_payout( &mut del_10, &mut bonded_pool, &mut reward_pool, @@ -996,7 +996,7 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 5)); // When - let payout = Pools::calculate_delegator_payout( + let payout = Pools::calculate_member_payout( &mut del_40, &mut bonded_pool, &mut reward_pool, @@ -1014,7 +1014,7 @@ mod claim_payout { assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75); // When - let payout = Pools::calculate_delegator_payout( + let payout = Pools::calculate_member_payout( &mut del_50, &mut bonded_pool, &mut reward_pool, @@ -1031,7 +1031,7 @@ mod claim_payout { // old pool points + points from new earnings - del points. // // points from new earnings = new earnings(50) * bonded_pool.points(100) - // del points = delegator.points(50) * new_earnings_since_last_claim (100) + // del points = member.points(50) * new_earnings_since_last_claim (100) (2_500 + 50 * 100) - 50 * 100, 200, ) @@ -1040,7 +1040,7 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 50)); // When - let payout = Pools::calculate_delegator_payout( + let payout = Pools::calculate_member_payout( &mut del_10, &mut bonded_pool, &mut reward_pool, @@ -1059,7 +1059,7 @@ mod claim_payout { assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420); // When - let payout = Pools::calculate_delegator_payout( + let payout = Pools::calculate_member_payout( &mut del_10, &mut bonded_pool, &mut reward_pool, @@ -1076,7 +1076,7 @@ mod claim_payout { // old pool points + points from new earnings - del points // // points from new earnings = new earnings(400) * bonded_pool.points(100) - // del points = delegator.points(10) * new_earnings_since_last_claim(400) + // del points = member.points(10) * new_earnings_since_last_claim(400) (2_000 + 400 * 100) - 10 * 400, 600 ) @@ -1089,7 +1089,7 @@ mod claim_payout { assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400); // When - let payout = Pools::calculate_delegator_payout( + let payout = Pools::calculate_member_payout( &mut del_10, &mut bonded_pool, &mut reward_pool, @@ -1104,7 +1104,7 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 2)); // When - let payout = Pools::calculate_delegator_payout( + let payout = Pools::calculate_member_payout( &mut del_40, &mut bonded_pool, &mut reward_pool, @@ -1119,7 +1119,7 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 188)); // When - let payout = Pools::calculate_delegator_payout( + let payout = Pools::calculate_member_payout( &mut del_50, &mut bonded_pool, &mut reward_pool, @@ -1136,7 +1136,7 @@ mod claim_payout { #[test] fn do_reward_payout_works() { ExtBuilder::default() - .add_delegators(vec![(40, 40), (50, 50)]) + .add_members(vec![(40, 40), (50, 50)]) .build_and_execute(|| { let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); @@ -1144,16 +1144,16 @@ mod claim_payout { // Given the bonded pool has 100 points assert_eq!(bonded_pool.points, 100); - // Each delegator currently has a free balance of + // Each member currently has a free balance of Balances::make_free_balance_be(&10, 0); Balances::make_free_balance_be(&40, 0); Balances::make_free_balance_be(&50, 0); // and the reward pool has earned 100 in rewards Balances::make_free_balance_be(&default_reward_account(), ed + 100); - let mut del_10 = Delegators::get(10).unwrap(); - let mut del_40 = Delegators::get(40).unwrap(); - let mut del_50 = Delegators::get(50).unwrap(); + let mut del_10 = PoolMembers::get(10).unwrap(); + let mut del_40 = PoolMembers::get(40).unwrap(); + let mut del_50 = PoolMembers::get(50).unwrap(); // When assert_ok!(Pools::do_reward_payout( @@ -1258,7 +1258,7 @@ mod claim_payout { // old pool points + points from new earnings - del points. // // points from new earnings = new earnings(50) * bonded_pool.points(100) - // del points = delegator.points(50) * new_earnings_since_last_claim (100) + // del points = member.points(50) * new_earnings_since_last_claim (100) (2_500 + 50 * 100) - 50 * 100, 200, ) @@ -1303,7 +1303,7 @@ mod claim_payout { // old pool points + points from new earnings - del points // // points from new earnings = new earnings(400) * bonded_pool.points(100) - // del points = delegator.points(10) * new_earnings_since_last_claim(400) + // del points = member.points(10) * new_earnings_since_last_claim(400) (2_000 + 400 * 100) - 10 * 400, 600 ) @@ -1386,7 +1386,7 @@ mod unbond { inner: BondedPoolInner { state: PoolState::Destroying, points: 0, - delegator_counter: 1, + member_counter: 1, roles: DEFAULT_ROLES, } } @@ -1399,7 +1399,7 @@ mod unbond { #[test] fn unbond_of_3_works() { ExtBuilder::default() - .add_delegators(vec![(40, 40), (550, 550)]) + .add_members(vec![(40, 40), (550, 550)]) .build_and_execute(|| { let ed = Balances::minimum_balance(); // Given a slash from 600 -> 100 @@ -1422,7 +1422,7 @@ mod unbond { inner: BondedPoolInner { state: PoolState::Open, points: 560, - delegator_counter: 3, + member_counter: 3, roles: DEFAULT_ROLES, } } @@ -1430,8 +1430,8 @@ mod unbond { assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 94); assert_eq!( - Delegators::::get(40).unwrap().unbonding_eras, - delegator_unbonding_eras!(0 + 3 => 40) + PoolMembers::::get(40).unwrap().unbonding_eras, + member_unbonding_eras!(0 + 3 => 40) ); assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding @@ -1451,15 +1451,15 @@ mod unbond { inner: BondedPoolInner { state: PoolState::Destroying, points: 10, - delegator_counter: 3, + member_counter: 3, roles: DEFAULT_ROLES } } ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 2); assert_eq!( - Delegators::::get(550).unwrap().unbonding_eras, - delegator_unbonding_eras!(0 + 3 => 550) + PoolMembers::::get(550).unwrap().unbonding_eras, + member_unbonding_eras!(0 + 3 => 550) ); assert_eq!(Balances::free_balance(&550), 550 + 550); @@ -1478,15 +1478,15 @@ mod unbond { inner: BondedPoolInner { state: PoolState::Destroying, points: 0, - delegator_counter: 3, + member_counter: 3, roles: DEFAULT_ROLES } } ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); assert_eq!( - Delegators::::get(550).unwrap().unbonding_eras, - delegator_unbonding_eras!(0 + 3 => 550) + PoolMembers::::get(550).unwrap().unbonding_eras, + member_unbonding_eras!(0 + 3 => 550) ); assert_eq!(Balances::free_balance(&550), 550 + 550); }); @@ -1534,7 +1534,7 @@ mod unbond { fn unbond_kick_works() { // Kick: the pool is blocked and the caller is either the root or state-toggler. ExtBuilder::default() - .add_delegators(vec![(100, 100), (200, 200)]) + .add_members(vec![(100, 100), (200, 200)]) .build_and_execute(|| { // Given unsafe_set_state(1, PoolState::Blocked).unwrap(); @@ -1563,7 +1563,7 @@ mod unbond { roles: DEFAULT_ROLES, state: PoolState::Blocked, points: 10, // Only 10 points because 200 + 100 was unbonded - delegator_counter: 3, + member_counter: 3, } } ); @@ -1588,7 +1588,7 @@ mod unbond { #[test] fn unbond_permissionless_works() { // Scenarios where non-admin accounts can unbond others - ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { + ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| { // Given the pool is blocked unsafe_set_state(1, PoolState::Blocked).unwrap(); @@ -1601,13 +1601,13 @@ mod unbond { // Given the pool is destroying unsafe_set_state(1, PoolState::Destroying).unwrap(); - // The depositor cannot be fully unbonded until they are the last delegator + // The depositor cannot be fully unbonded until they are the last member assert_noop!( Pools::fully_unbond(Origin::signed(10), 10), - Error::::NotOnlyDelegator + Error::::NotOnlyPoolMember ); - // Any account can unbond a delegator that is not the depositor + // Any account can unbond a member that is not the depositor assert_ok!(Pools::fully_unbond(Origin::signed(420), 100)); // Given the pool is blocked @@ -1650,12 +1650,12 @@ mod unbond { ExtBuilder::default().build_and_execute(|| { assert_noop!( Pools::fully_unbond(Origin::signed(11), 11), - Error::::DelegatorNotFound + Error::::PoolMemberNotFound ); - // Add the delegator - let delegator = Delegator { pool_id: 2, points: 10, ..Default::default() }; - Delegators::::insert(11, delegator); + // Add the member + let member = PoolMember { pool_id: 2, points: 10, ..Default::default() }; + PoolMembers::::insert(11, member); let _ = Pools::fully_unbond(Origin::signed(11), 11); }); @@ -1665,14 +1665,14 @@ mod unbond { #[should_panic = "Defensive failure has been triggered!"] fn unbond_panics_when_reward_pool_not_found() { ExtBuilder::default().build_and_execute(|| { - let delegator = Delegator { pool_id: 2, points: 10, ..Default::default() }; - Delegators::::insert(11, delegator); + let member = PoolMember { pool_id: 2, points: 10, ..Default::default() }; + PoolMembers::::insert(11, member); BondedPool:: { id: 1, inner: BondedPoolInner { state: PoolState::Open, points: 10, - delegator_counter: 1, + member_counter: 1, roles: DEFAULT_ROLES, }, } @@ -1686,12 +1686,12 @@ mod unbond { fn partial_unbond_era_tracking() { ExtBuilder::default().build_and_execute(|| { // given - assert_eq!(Delegators::::get(10).unwrap().active_points(), 10); - assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 0); - assert_eq!(Delegators::::get(10).unwrap().pool_id, 1); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); + assert_eq!(PoolMembers::::get(10).unwrap().pool_id, 1); assert_eq!( - Delegators::::get(10).unwrap().unbonding_eras, - delegator_unbonding_eras!() + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!() ); assert_eq!(BondedPool::::get(1).unwrap().points, 10); assert!(SubPoolsStorage::::get(1).is_none()); @@ -1705,12 +1705,12 @@ mod unbond { assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); // then - assert_eq!(Delegators::::get(10).unwrap().active_points(), 9); - assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 1); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 9); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 1); assert_eq!(BondedPool::::get(1).unwrap().points, 9); assert_eq!( - Delegators::::get(10).unwrap().unbonding_eras, - delegator_unbonding_eras!(3 => 1) + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 1) ); assert_eq!( SubPoolsStorage::::get(1).unwrap(), @@ -1726,12 +1726,12 @@ mod unbond { assert_ok!(Pools::unbond(Origin::signed(10), 10, 5)); // then - assert_eq!(Delegators::::get(10).unwrap().active_points(), 4); - assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 6); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 4); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 6); assert_eq!(BondedPool::::get(1).unwrap().points, 4); assert_eq!( - Delegators::::get(10).unwrap().unbonding_eras, - delegator_unbonding_eras!(3 => 6) + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6) ); assert_eq!( SubPoolsStorage::::get(1).unwrap(), @@ -1748,12 +1748,12 @@ mod unbond { assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); // then - assert_eq!(Delegators::::get(10).unwrap().active_points(), 3); - assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 7); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 3); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 7); assert_eq!(BondedPool::::get(1).unwrap().points, 3); assert_eq!( - Delegators::::get(10).unwrap().unbonding_eras, - delegator_unbonding_eras!(3 => 6, 4 => 1) + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6, 4 => 1) ); assert_eq!( SubPoolsStorage::::get(1).unwrap(), @@ -1775,12 +1775,12 @@ mod unbond { assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); // then - assert_eq!(Delegators::::get(10).unwrap().active_points(), 0); - assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 10); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 0); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 10); assert_eq!(BondedPool::::get(1).unwrap().points, 0); assert_eq!( - Delegators::::get(10).unwrap().unbonding_eras, - delegator_unbonding_eras!(3 => 6, 4 => 4) + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6, 4 => 4) ); assert_eq!( SubPoolsStorage::::get(1).unwrap(), @@ -1807,8 +1807,8 @@ mod unbond { CurrentEra::set(1); assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); assert_eq!( - Delegators::::get(10).unwrap().unbonding_eras, - delegator_unbonding_eras!(3 => 2, 4 => 3) + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 2, 4 => 3) ); // when @@ -1822,8 +1822,8 @@ mod unbond { MaxUnbonding::set(3); assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); assert_eq!( - Delegators::::get(10).unwrap().unbonding_eras, - delegator_unbonding_eras!(3 => 2, 4 => 3, 5 => 1) + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 2, 4 => 3, 5 => 1) ); }) } @@ -1833,22 +1833,22 @@ mod unbond { fn depositor_permissioned_partial_unbond() { ExtBuilder::default() .ed(1) - .add_delegators(vec![(100, 100)]) + .add_members(vec![(100, 100)]) .build_and_execute(|| { // given assert_eq!(MinCreateBond::::get(), 2); - assert_eq!(Delegators::::get(10).unwrap().active_points(), 10); - assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 0); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); // can unbond a bit.. assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); - assert_eq!(Delegators::::get(10).unwrap().active_points(), 7); - assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 3); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 7); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 3); // but not less than 2 assert_noop!( Pools::unbond(Origin::signed(10), 10, 6), - Error::::NotOnlyDelegator + Error::::NotOnlyPoolMember ); }); } @@ -1858,12 +1858,12 @@ mod unbond { fn depositor_permissioned_partial_unbond_slashed() { ExtBuilder::default() .ed(1) - .add_delegators(vec![(100, 100)]) + .add_members(vec![(100, 100)]) .build_and_execute(|| { // given assert_eq!(MinCreateBond::::get(), 2); - assert_eq!(Delegators::::get(10).unwrap().active_points(), 10); - assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 0); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); // slash the default pool StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 5); @@ -1871,7 +1871,7 @@ mod unbond { // cannot unbond even 7, because the value of shares is now less. assert_noop!( Pools::unbond(Origin::signed(10), 10, 7), - Error::::NotOnlyDelegator + Error::::NotOnlyPoolMember ); }); } @@ -1908,7 +1908,7 @@ mod withdraw_unbonded { #[test] fn withdraw_unbonded_works_against_slashed_no_era_sub_pool() { ExtBuilder::default() - .add_delegators(vec![(40, 40), (550, 550)]) + .add_members(vec![(40, 40), (550, 550)]) .build_and_execute(|| { // Given assert_eq!(StakingMock::bonding_duration(), 3); @@ -1964,7 +1964,7 @@ mod withdraw_unbonded { ); assert_eq!(Balances::free_balance(&550), 550 + 545); assert_eq!(Balances::free_balance(&default_bonded_account()), 50); - assert!(!Delegators::::contains_key(550)); + assert!(!PoolMembers::::contains_key(550)); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 40, 0)); @@ -1976,7 +1976,7 @@ mod withdraw_unbonded { ); assert_eq!(Balances::free_balance(&40), 40 + 40); assert_eq!(Balances::free_balance(&default_bonded_account()), 50 - 40); - assert!(!Delegators::::contains_key(40)); + assert!(!PoolMembers::::contains_key(40)); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); @@ -1984,7 +1984,7 @@ mod withdraw_unbonded { // Then assert_eq!(Balances::free_balance(&10), 10 + 10); assert_eq!(Balances::free_balance(&default_bonded_account()), 0); - assert!(!Delegators::::contains_key(10)); + assert!(!PoolMembers::::contains_key(10)); // Pools are removed from storage because the depositor left assert!(!SubPoolsStorage::::contains_key(1),); assert!(!RewardPools::::contains_key(1),); @@ -1993,11 +1993,11 @@ mod withdraw_unbonded { } // This test also documents the case when the pools free balance goes below ED before all - // delegators have unbonded. + // members have unbonded. #[test] fn withdraw_unbonded_works_against_slashed_with_era_sub_pools() { ExtBuilder::default() - .add_delegators(vec![(40, 40), (550, 550)]) + .add_members(vec![(40, 40), (550, 550)]) .build_and_execute(|| { // Given StakingMock::set_bonded_balance(default_bonded_account(), 100); // slash bonded balance @@ -2028,7 +2028,7 @@ mod withdraw_unbonded { ); assert_eq!(Balances::free_balance(&40), 40 + 6); assert_eq!(Balances::free_balance(&default_bonded_account()), 94); - assert!(!Delegators::::contains_key(40)); + assert!(!PoolMembers::::contains_key(40)); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 550, 0)); @@ -2041,7 +2041,7 @@ mod withdraw_unbonded { assert_eq!(Balances::free_balance(&550), 550 + 92); // The account was dusted because it went below ED(5) assert_eq!(Balances::free_balance(&default_bonded_account()), 0); - assert!(!Delegators::::contains_key(550)); + assert!(!PoolMembers::::contains_key(550)); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); @@ -2049,7 +2049,7 @@ mod withdraw_unbonded { // Then assert_eq!(Balances::free_balance(&10), 10 + 0); assert_eq!(Balances::free_balance(&default_bonded_account()), 0); - assert!(!Delegators::::contains_key(10)); + assert!(!PoolMembers::::contains_key(10)); // Pools are removed from storage because the depositor left assert!(!SubPoolsStorage::::contains_key(1),); assert!(!RewardPools::::contains_key(1),); @@ -2098,15 +2098,15 @@ mod withdraw_unbonded { assert_noop!( Pools::withdraw_unbonded(Origin::signed(11), 11, 0), - Error::::DelegatorNotFound + Error::::PoolMemberNotFound ); - let mut delegator = Delegator { pool_id: 1, points: 10, ..Default::default() }; - Delegators::::insert(11, delegator.clone()); + let mut member = PoolMember { pool_id: 1, points: 10, ..Default::default() }; + PoolMembers::::insert(11, member.clone()); // Simulate calling `unbond` - delegator.unbonding_eras = delegator_unbonding_eras!(3 + 0 => 10); - Delegators::::insert(11, delegator.clone()); + member.unbonding_eras = member_unbonding_eras!(3 + 0 => 10); + PoolMembers::::insert(11, member.clone()); // We are still in the bonding duration assert_noop!( @@ -2114,8 +2114,8 @@ mod withdraw_unbonded { Error::::CannotWithdrawAny ); - // If we error the delegator does not get removed - assert_eq!(Delegators::::get(&11), Some(delegator)); + // If we error the member does not get removed + assert_eq!(PoolMembers::::get(&11), Some(member)); // and the sub pools do not get updated. assert_eq!(SubPoolsStorage::::get(1).unwrap(), sub_pools) }); @@ -2124,7 +2124,7 @@ mod withdraw_unbonded { #[test] fn withdraw_unbonded_kick() { ExtBuilder::default() - .add_delegators(vec![(100, 100), (200, 200)]) + .add_members(vec![(100, 100), (200, 200)]) .build_and_execute(|| { // Given assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); @@ -2136,7 +2136,7 @@ mod withdraw_unbonded { inner: BondedPoolInner { points: 10, state: PoolState::Open, - delegator_counter: 3, + member_counter: 3, roles: DEFAULT_ROLES } } @@ -2166,15 +2166,15 @@ mod withdraw_unbonded { assert_eq!(Balances::free_balance(100), 100 + 100); assert_eq!(Balances::free_balance(200), 200 + 200); - assert!(!Delegators::::contains_key(100)); - assert!(!Delegators::::contains_key(200)); + assert!(!PoolMembers::::contains_key(100)); + assert!(!PoolMembers::::contains_key(200)); assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); }); } #[test] fn withdraw_unbonded_destroying_permissionless() { - ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { + ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| { // Given assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); assert_eq!( @@ -2184,7 +2184,7 @@ mod withdraw_unbonded { inner: BondedPoolInner { points: 10, state: PoolState::Open, - delegator_counter: 2, + member_counter: 2, roles: DEFAULT_ROLES, } } @@ -2201,19 +2201,19 @@ mod withdraw_unbonded { // Given unsafe_set_state(1, PoolState::Destroying).unwrap(); - // Can permissionlesly withdraw a delegator that is not the depositor + // Can permissionlesly withdraw a member that is not the depositor assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0)); assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default(),); assert_eq!(Balances::free_balance(100), 100 + 100); - assert!(!Delegators::::contains_key(100)); + assert!(!PoolMembers::::contains_key(100)); }); } #[test] fn withdraw_unbonded_depositor_with_era_pool() { ExtBuilder::default() - .add_delegators(vec![(100, 100), (200, 200)]) + .add_members(vec![(100, 100), (200, 200)]) .build_and_execute(|| { // Given assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); @@ -2236,14 +2236,14 @@ mod withdraw_unbonded { } ); - // Skip ahead eras to where its valid for the delegators to withdraw + // Skip ahead eras to where its valid for the members to withdraw current_era += StakingMock::bonding_duration(); CurrentEra::set(current_era); - // Cannot withdraw the depositor if their is a delegator in another `with_era` pool. + // Cannot withdraw the depositor if their is a member in another `with_era` pool. assert_noop!( Pools::withdraw_unbonded(Origin::signed(420), 10, 0), - Error::::NotOnlyDelegator + Error::::NotOnlyPoolMember ); // Given @@ -2259,10 +2259,10 @@ mod withdraw_unbonded { } ); - // Cannot withdraw the depositor if their is a delegator in another `with_era` pool. + // Cannot withdraw the depositor if their is a member in another `with_era` pool. assert_noop!( Pools::withdraw_unbonded(Origin::signed(420), 10, 0), - Error::::NotOnlyDelegator + Error::::NotOnlyPoolMember ); // Given @@ -2279,7 +2279,7 @@ mod withdraw_unbonded { // The depositor can withdraw assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 10, 0)); - assert!(!Delegators::::contains_key(10)); + assert!(!PoolMembers::::contains_key(10)); assert_eq!(Balances::free_balance(10), 10 + 10); // Pools are removed from storage because the depositor left assert!(!SubPoolsStorage::::contains_key(1)); @@ -2290,7 +2290,7 @@ mod withdraw_unbonded { #[test] fn withdraw_unbonded_depositor_no_era_pool() { - ExtBuilder::default().add_delegators(vec![(100, 100)]).build_and_execute(|| { + ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| { // Given assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); unsafe_set_state(1, PoolState::Destroying).unwrap(); @@ -2312,10 +2312,10 @@ mod withdraw_unbonded { } ); - // Cannot withdraw depositor with another delegator in the `no_era` pool + // Cannot withdraw depositor with another member in the `no_era` pool assert_noop!( Pools::withdraw_unbonded(Origin::signed(420), 10, 0), - Error::::NotOnlyDelegator + Error::::NotOnlyPoolMember ); // Given @@ -2330,7 +2330,7 @@ mod withdraw_unbonded { // The depositor can withdraw assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 10, 0)); - assert!(!Delegators::::contains_key(10)); + assert!(!PoolMembers::::contains_key(10)); assert_eq!(Balances::free_balance(10), 10 + 10); // Pools are removed from storage because the depositor left assert!(!SubPoolsStorage::::contains_key(1)); @@ -2350,8 +2350,8 @@ mod withdraw_unbonded { CurrentEra::set(1); assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); assert_eq!( - Delegators::::get(10).unwrap().unbonding_eras, - delegator_unbonding_eras!(3 => 6, 4 => 1) + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6, 4 => 1) ); assert_eq!( SubPoolsStorage::::get(1).unwrap(), @@ -2363,17 +2363,17 @@ mod withdraw_unbonded { } } ); - assert_eq!(Delegators::::get(10).unwrap().active_points(), 3); - assert_eq!(Delegators::::get(10).unwrap().unbonding_points(), 7); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 3); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 7); assert_eq!( pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { delegator: 10, pool_id: 1, bonded: 10, joined: true }, - Event::PaidOut { delegator: 10, pool_id: 1, payout: 0 }, - Event::Unbonded { delegator: 10, pool_id: 1, amount: 6 }, - Event::PaidOut { delegator: 10, pool_id: 1, payout: 0 }, - Event::Unbonded { delegator: 10, pool_id: 1, amount: 1 } + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 0 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 6 }, + Event::PaidOut { member: 10, pool_id: 1, payout: 0 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 1 } ] ); @@ -2390,8 +2390,8 @@ mod withdraw_unbonded { // then assert_eq!( - Delegators::::get(10).unwrap().unbonding_eras, - delegator_unbonding_eras!(4 => 1) + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(4 => 1) ); assert_eq!( SubPoolsStorage::::get(1).unwrap(), @@ -2404,7 +2404,7 @@ mod withdraw_unbonded { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Withdrawn { delegator: 10, pool_id: 1, amount: 6 }] + vec![Event::Withdrawn { member: 10, pool_id: 1, amount: 6 }] ); // when @@ -2413,13 +2413,13 @@ mod withdraw_unbonded { // then assert_eq!( - Delegators::::get(10).unwrap().unbonding_eras, - delegator_unbonding_eras!() + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!() ); assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); assert_eq!( pool_events_since_last_call(), - vec![Event::Withdrawn { delegator: 10, pool_id: 1, amount: 1 },] + vec![Event::Withdrawn { member: 10, pool_id: 1, amount: 1 },] ); // when repeating: @@ -2432,14 +2432,14 @@ mod withdraw_unbonded { #[test] fn partial_withdraw_unbonded_non_depositor() { - ExtBuilder::default().add_delegators(vec![(11, 10)]).build_and_execute(|| { + ExtBuilder::default().add_members(vec![(11, 10)]).build_and_execute(|| { // given assert_ok!(Pools::unbond(Origin::signed(11), 11, 6)); CurrentEra::set(1); assert_ok!(Pools::unbond(Origin::signed(11), 11, 1)); assert_eq!( - Delegators::::get(11).unwrap().unbonding_eras, - delegator_unbonding_eras!(3 => 6, 4 => 1) + PoolMembers::::get(11).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6, 4 => 1) ); assert_eq!( SubPoolsStorage::::get(1).unwrap(), @@ -2451,18 +2451,18 @@ mod withdraw_unbonded { } } ); - assert_eq!(Delegators::::get(11).unwrap().active_points(), 3); - assert_eq!(Delegators::::get(11).unwrap().unbonding_points(), 7); + assert_eq!(PoolMembers::::get(11).unwrap().active_points(), 3); + assert_eq!(PoolMembers::::get(11).unwrap().unbonding_points(), 7); assert_eq!( pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { delegator: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { delegator: 11, pool_id: 1, bonded: 10, joined: true }, - Event::PaidOut { delegator: 11, pool_id: 1, payout: 0 }, - Event::Unbonded { delegator: 11, pool_id: 1, amount: 6 }, - Event::PaidOut { delegator: 11, pool_id: 1, payout: 0 }, - Event::Unbonded { delegator: 11, pool_id: 1, amount: 1 } + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 11, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 11, pool_id: 1, payout: 0 }, + Event::Unbonded { member: 11, pool_id: 1, amount: 6 }, + Event::PaidOut { member: 11, pool_id: 1, payout: 0 }, + Event::Unbonded { member: 11, pool_id: 1, amount: 1 } ] ); @@ -2479,8 +2479,8 @@ mod withdraw_unbonded { // then assert_eq!( - Delegators::::get(11).unwrap().unbonding_eras, - delegator_unbonding_eras!(4 => 1) + PoolMembers::::get(11).unwrap().unbonding_eras, + member_unbonding_eras!(4 => 1) ); assert_eq!( SubPoolsStorage::::get(1).unwrap(), @@ -2493,7 +2493,7 @@ mod withdraw_unbonded { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Withdrawn { delegator: 11, pool_id: 1, amount: 6 }] + vec![Event::Withdrawn { member: 11, pool_id: 1, amount: 6 }] ); // when @@ -2502,13 +2502,13 @@ mod withdraw_unbonded { // then assert_eq!( - Delegators::::get(11).unwrap().unbonding_eras, - delegator_unbonding_eras!() + PoolMembers::::get(11).unwrap().unbonding_eras, + member_unbonding_eras!() ); assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); assert_eq!( pool_events_since_last_call(), - vec![Event::Withdrawn { delegator: 11, pool_id: 1, amount: 1 }] + vec![Event::Withdrawn { member: 11, pool_id: 1, amount: 1 }] ); // when repeating: @@ -2532,7 +2532,7 @@ mod create { assert!(!BondedPools::::contains_key(2)); assert!(!RewardPools::::contains_key(2)); - assert!(!Delegators::::contains_key(11)); + assert!(!PoolMembers::::contains_key(11)); assert_eq!(StakingMock::active_stake(&next_pool_stash), None); Balances::make_free_balance_be(&11, StakingMock::minimum_bond() + ed); @@ -2546,8 +2546,8 @@ mod create { assert_eq!(Balances::free_balance(&11), 0); assert_eq!( - Delegators::::get(11).unwrap(), - Delegator { pool_id: 2, points: StakingMock::minimum_bond(), ..Default::default() } + PoolMembers::::get(11).unwrap(), + PoolMember { pool_id: 2, points: StakingMock::minimum_bond(), ..Default::default() } ); assert_eq!( BondedPool::::get(2).unwrap(), @@ -2555,7 +2555,7 @@ mod create { id: 2, inner: BondedPoolInner { points: StakingMock::minimum_bond(), - delegator_counter: 1, + member_counter: 1, state: PoolState::Open, roles: PoolRoles { depositor: 11, @@ -2614,7 +2614,7 @@ mod create { inner: BondedPoolInner { state: PoolState::Open, points: 10, - delegator_counter: 1, + member_counter: 1, roles: DEFAULT_ROLES, }, } @@ -2629,15 +2629,15 @@ mod create { ); // Given - assert_eq!(Delegators::::count(), 1); + assert_eq!(PoolMembers::::count(), 1); MaxPools::::put(3); - MaxDelegators::::put(1); + MaxPoolMembers::::put(1); Balances::make_free_balance_be(&11, 5 + 20); // Then assert_noop!( Pools::create(Origin::signed(11), 20, 11, 11, 11), - Error::::MaxDelegators + Error::::MaxPoolMembers ); }); } @@ -2799,8 +2799,8 @@ mod set_configs { assert_eq!(MinJoinBond::::get(), 1); assert_eq!(MinCreateBond::::get(), 2); assert_eq!(MaxPools::::get(), Some(3)); - assert_eq!(MaxDelegators::::get(), Some(4)); - assert_eq!(MaxDelegatorsPerPool::::get(), Some(5)); + assert_eq!(MaxPoolMembers::::get(), Some(4)); + assert_eq!(MaxPoolMembersPerPool::::get(), Some(5)); // Noop does nothing assert_storage_noop!(assert_ok!(Pools::set_configs( @@ -2824,8 +2824,8 @@ mod set_configs { assert_eq!(MinJoinBond::::get(), 0); assert_eq!(MinCreateBond::::get(), 0); assert_eq!(MaxPools::::get(), None); - assert_eq!(MaxDelegators::::get(), None); - assert_eq!(MaxDelegatorsPerPool::::get(), None); + assert_eq!(MaxPoolMembers::::get(), None); + assert_eq!(MaxPoolMembersPerPool::::get(), None); }); } } @@ -2837,11 +2837,11 @@ mod bond_extra { #[test] fn bond_extra_from_free_balance_creator() { ExtBuilder::default().build_and_execute(|| { - // 10 is the owner and a delegator in pool 1, give them some more funds. + // 10 is the owner and a member in pool 1, give them some more funds. Balances::make_free_balance_be(&10, 100); // given - assert_eq!(Delegators::::get(10).unwrap().points, 10); + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); assert_eq!(BondedPools::::get(1).unwrap().points, 10); assert_eq!(Balances::free_balance(10), 100); @@ -2850,15 +2850,15 @@ mod bond_extra { // then assert_eq!(Balances::free_balance(10), 90); - assert_eq!(Delegators::::get(10).unwrap().points, 20); + assert_eq!(PoolMembers::::get(10).unwrap().points, 20); assert_eq!(BondedPools::::get(1).unwrap().points, 20); assert_eq!( pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { delegator: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { delegator: 10, pool_id: 1, bonded: 10, joined: false } + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false } ] ); @@ -2867,12 +2867,12 @@ mod bond_extra { // then assert_eq!(Balances::free_balance(10), 70); - assert_eq!(Delegators::::get(10).unwrap().points, 40); + assert_eq!(PoolMembers::::get(10).unwrap().points, 40); assert_eq!(BondedPools::::get(1).unwrap().points, 40); assert_eq!( pool_events_since_last_call(), - vec![Event::Bonded { delegator: 10, pool_id: 1, bonded: 20, joined: false }] + vec![Event::Bonded { member: 10, pool_id: 1, bonded: 20, joined: false }] ); }) } @@ -2881,13 +2881,13 @@ mod bond_extra { fn bond_extra_from_rewards_creator() { ExtBuilder::default().build_and_execute(|| { // put some money in the reward account, all of which will belong to 10 as the only - // delegator of the pool. + // member of the pool. Balances::make_free_balance_be(&default_reward_account(), 7); // ... if which only 2 is claimable to make sure the reward account does not die. let claimable_reward = 7 - ExistentialDeposit::get(); // given - assert_eq!(Delegators::::get(10).unwrap().points, 10); + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); assert_eq!(BondedPools::::get(1).unwrap().points, 10); assert_eq!(Balances::free_balance(10), 5); @@ -2896,17 +2896,17 @@ mod bond_extra { // then assert_eq!(Balances::free_balance(10), 5); - assert_eq!(Delegators::::get(10).unwrap().points, 10 + claimable_reward); + assert_eq!(PoolMembers::::get(10).unwrap().points, 10 + claimable_reward); assert_eq!(BondedPools::::get(1).unwrap().points, 10 + claimable_reward); assert_eq!( pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { delegator: 10, pool_id: 1, bonded: 10, joined: true }, - Event::PaidOut { delegator: 10, pool_id: 1, payout: claimable_reward }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: claimable_reward }, Event::Bonded { - delegator: 10, + member: 10, pool_id: 1, bonded: claimable_reward, joined: false @@ -2918,9 +2918,9 @@ mod bond_extra { #[test] fn bond_extra_from_rewards_joiner() { - ExtBuilder::default().add_delegators(vec![(20, 20)]).build_and_execute(|| { + ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { // put some money in the reward account, all of which will belong to 10 as the only - // delegator of the pool. + // member of the pool. Balances::make_free_balance_be(&default_reward_account(), 8); // ... if which only 3 is claimable to make sure the reward account does not die. let claimable_reward = 8 - ExistentialDeposit::get(); @@ -2928,8 +2928,8 @@ mod bond_extra { assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3"); // given - assert_eq!(Delegators::::get(10).unwrap().points, 10); - assert_eq!(Delegators::::get(20).unwrap().points, 20); + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); + assert_eq!(PoolMembers::::get(20).unwrap().points, 20); assert_eq!(BondedPools::::get(1).unwrap().points, 30); assert_eq!(Balances::free_balance(10), 5); assert_eq!(Balances::free_balance(20), 20); @@ -2940,7 +2940,7 @@ mod bond_extra { // then assert_eq!(Balances::free_balance(10), 5); // 10's share of the reward is 1/3, since they gave 10/30 of the total shares. - assert_eq!(Delegators::::get(10).unwrap().points, 10 + 1); + assert_eq!(PoolMembers::::get(10).unwrap().points, 10 + 1); assert_eq!(BondedPools::::get(1).unwrap().points, 30 + 1); // when @@ -2950,19 +2950,19 @@ mod bond_extra { assert_eq!(Balances::free_balance(20), 20); // 20's share of the rewards is the other 2/3 of the rewards, since they have 20/30 of // the shares - assert_eq!(Delegators::::get(20).unwrap().points, 20 + 2); + assert_eq!(PoolMembers::::get(20).unwrap().points, 20 + 2); assert_eq!(BondedPools::::get(1).unwrap().points, 30 + 3); assert_eq!( pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { delegator: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { delegator: 20, pool_id: 1, bonded: 20, joined: true }, - Event::PaidOut { delegator: 10, pool_id: 1, payout: 1 }, - Event::Bonded { delegator: 10, pool_id: 1, bonded: 1, joined: false }, - Event::PaidOut { delegator: 20, pool_id: 1, payout: 2 }, - Event::Bonded { delegator: 20, pool_id: 1, bonded: 2, joined: false } + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 1, joined: false }, + Event::PaidOut { member: 20, pool_id: 1, payout: 2 }, + Event::Bonded { member: 20, pool_id: 1, bonded: 2, joined: false } ] ); }) diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index 57f21352d6bd1..efd45eb6f08d0 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -62,7 +62,7 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: NominationPools MinJoinBond (r:1 w:0) - // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:0) @@ -71,7 +71,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) - // Storage: NominationPools CounterForDelegators (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn join() -> Weight { (109_762_000 as Weight) .saturating_add(T::DbWeight::get().reads(15 as Weight)) @@ -87,7 +87,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(15 as Weight)) .saturating_add(T::DbWeight::get().writes(11 as Weight)) } - // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:0) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) @@ -96,7 +96,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } - // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:2 w:1) @@ -125,14 +125,14 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } - // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) - // Storage: NominationPools CounterForDelegators (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { (66_522_000 as Weight) // Standard Error: 0 @@ -151,7 +151,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools MinCreateBond (r:1 w:0) // Storage: NominationPools MaxPools (r:1 w:0) // Storage: NominationPools CounterForBondedPools (r:1 w:1) - // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: System ParentHash (r:1 w:0) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: NominationPools BondedPools (r:1 w:1) @@ -161,7 +161,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking HistoryDepth (r:1 w:0) // Storage: Balances Locks (r:1 w:1) - // Storage: NominationPools CounterForDelegators (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: NominationPools CounterForRewardPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) @@ -207,7 +207,7 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { // Storage: NominationPools MinJoinBond (r:1 w:0) - // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:0) @@ -216,7 +216,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) - // Storage: NominationPools CounterForDelegators (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn join() -> Weight { (109_762_000 as Weight) .saturating_add(RocksDbWeight::get().reads(15 as Weight)) @@ -232,7 +232,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(15 as Weight)) .saturating_add(RocksDbWeight::get().writes(11 as Weight)) } - // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:0) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) @@ -241,7 +241,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } - // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:2 w:1) @@ -270,14 +270,14 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } - // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) - // Storage: NominationPools CounterForDelegators (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn withdraw_unbonded_update(s: u32) -> Weight { (66_522_000 as Weight) // Standard Error: 0 @@ -285,7 +285,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } - // Storage: NominationPools CounterForDelegators (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn withdraw_unbonded_kill(s: u32) -> Weight { (66_522_000 as Weight) // Standard Error: 0 @@ -297,7 +297,7 @@ impl WeightInfo for () { // Storage: NominationPools MinCreateBond (r:1 w:0) // Storage: NominationPools MaxPools (r:1 w:0) // Storage: NominationPools CounterForBondedPools (r:1 w:1) - // Storage: NominationPools Delegators (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: System ParentHash (r:1 w:0) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: NominationPools BondedPools (r:1 w:1) @@ -307,7 +307,7 @@ impl WeightInfo for () { // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking HistoryDepth (r:1 w:0) // Storage: Balances Locks (r:1 w:1) - // Storage: NominationPools CounterForDelegators (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: NominationPools CounterForRewardPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) From 1e50f3dea3ccb032161d6cc4214931e68c4e0cba Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 21 Apr 2022 15:46:19 -0700 Subject: [PATCH 288/299] fmt --- .../nomination-pools/benchmarking/src/lib.rs | 4 +- frame/nomination-pools/src/lib.rs | 43 ++-- frame/nomination-pools/src/tests.rs | 208 +++++++----------- 3 files changed, 101 insertions(+), 154 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index aa066d2aaa8d7..84d62535b6792 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -8,9 +8,9 @@ use frame_election_provider_support::SortedListProvider; use frame_support::{ensure, traits::Get}; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ - BalanceOf, BondExtra, BondedPoolInner, BondedPools, ConfigOp, PoolMembers, MaxPoolMembers, + BalanceOf, BondExtra, BondedPoolInner, BondedPools, ConfigOp, MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, Pallet as Pools, - PoolRoles, PoolState, RewardPools, SubPoolsStorage, + PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage, }; use sp_runtime::traits::{Bounded, Zero}; use sp_staking::{EraIndex, StakingInterface}; diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 1aeb02d16f423..b4da4aa93b51a 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -89,8 +89,8 @@ //! //! A pool has 3 administrative roles (see [`PoolRoles`]): //! -//! * Depositor: creates the pool and is the initial member. They can only leave the pool once -//! all other members have left. Once they fully leave the pool is destroyed. +//! * Depositor: creates the pool and is the initial member. They can only leave the pool once all +//! other members have left. Once they fully leave the pool is destroyed. //! * Nominator: can select which validators the pool nominates. //! * State-Toggler: can change the pools state and kick members if the pool is blocked. //! * Root: can change the nominator, state-toggler, or itself and can perform any of the actions @@ -182,9 +182,9 @@ //! new_earnings = current_total_earnings - reward_pool.total_earnings; //! current_points = reward_pool.points + bonding_pool.points * new_earnings; //! ``` -//! * Finally, the`member_virtual_points` are computed: the product of the member's points -//! in the bonding pool and the total inflow of balance units since the last time the -//! member claimed rewards +//! * Finally, the`member_virtual_points` are computed: the product of the member's points in +//! the bonding pool and the total inflow of balance units since the last time the member +//! claimed rewards //! ```text //! new_earnings_since_last_claim = current_total_earnings - member.reward_pool_total_earnings; //! member_virtual_points = member.points * new_earnings_since_last_claim; @@ -295,8 +295,8 @@ //! ### Limitations //! //! * PoolMembers cannot vote with their staked funds because they are transferred into the pools -//! account. In the future this can be overcome by allowing the members to vote with their -//! bonded funds via vote splitting. +//! account. In the future this can be overcome by allowing the members to vote with their bonded +//! funds via vote splitting. //! * PoolMembers cannot quickly transfer to another pool if they do no like nominations, instead //! they must wait for the unbonding duration. //! @@ -1110,7 +1110,8 @@ pub mod pallet { /// Active members. #[pallet::storage] - pub type PoolMembers = CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember>; + pub type PoolMembers = + CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember>; /// Storage for bonded pools. // To get or insert a pool see [`BondedPool::get`] and [`BondedPool::put`] @@ -1334,8 +1335,7 @@ pub mod pallet { #[transactional] pub fn bond_extra(origin: OriginFor, extra: BondExtra>) -> DispatchResult { let who = ensure_signed(origin)?; - let (mut member, mut bonded_pool, mut reward_pool) = - Self::get_member_with_pools(&who)?; + let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; let (points_issued, bonded) = match extra { BondExtra::FreeBalance(amount) => @@ -1374,11 +1374,9 @@ pub mod pallet { #[transactional] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let (mut member, mut bonded_pool, mut reward_pool) = - Self::get_member_with_pools(&who)?; + let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; - let _ = - Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; + let _ = Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; Self::put_member_with_pools(&who, member, bonded_pool, reward_pool); Ok(()) @@ -1396,8 +1394,8 @@ pub mod pallet { /// * The pool is blocked and the caller is either the root or state-toggler. This is /// refereed to as a kick. /// * The pool is destroying and the member is not the depositor. - /// * The pool is destroying, the member is the depositor and no other members are in - /// the pool. + /// * The pool is destroying, the member is the depositor and no other members are in the + /// pool. /// /// ## Conditions for permissioned dispatch (i.e. the caller is also the /// `member_account`): @@ -1423,12 +1421,7 @@ pub mod pallet { let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&member_account)?; - bonded_pool.ok_to_unbond_with( - &caller, - &member_account, - &member, - unbonding_points, - )?; + bonded_pool.ok_to_unbond_with(&caller, &member_account, &member, unbonding_points)?; // Claim the the payout prior to unbonding. Once the user is unbonding their points // no longer exist in the bonded pool and thus they can no longer claim their payouts. @@ -2049,8 +2042,7 @@ impl Pallet { ensure!(!member.active_points().is_zero(), Error::::FullyUnbonding); let was_destroying = bonded_pool.is_destroying(); - let member_payout = - Self::calculate_member_payout(member, bonded_pool, reward_pool)?; + let member_payout = Self::calculate_member_payout(member, bonded_pool, reward_pool)?; // Transfer payout to the member. T::Currency::transfer( @@ -2196,8 +2188,7 @@ impl Pallet { origin: frame_system::pallet_prelude::OriginFor, member: T::AccountId, ) -> DispatchResult { - let points = - PoolMembers::::get(&member).map(|d| d.active_points()).unwrap_or_default(); + let points = PoolMembers::::get(&member).map(|d| d.active_points()).unwrap_or_default(); Self::unbond(origin, member, points) } } diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 3855e2372f030..6119b354b8d25 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -535,7 +535,10 @@ mod join { Balances::make_free_balance_be(&103, 100 + Balances::minimum_balance()); // Then - assert_noop!(Pools::join(Origin::signed(103), 100, 1), Error::::MaxPoolMembers); + assert_noop!( + Pools::join(Origin::signed(103), 100, 1), + Error::::MaxPoolMembers + ); // Given assert_eq!(PoolMembers::::count(), 3); @@ -841,12 +844,9 @@ mod claim_payout { // Given no rewards have been earned // When - let payout = Pools::calculate_member_payout( - &mut member, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut member, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 0); @@ -857,12 +857,9 @@ mod claim_payout { Balances::make_free_balance_be(&default_reward_account(), ed + 5); // When - let payout = Pools::calculate_member_payout( - &mut member, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut member, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 5); // (10 * 5 del virtual points / 10 * 5 pool points) * 5 pool balance @@ -873,12 +870,9 @@ mod claim_payout { Balances::make_free_balance_be(&default_reward_account(), ed + 10); // When - let payout = Pools::calculate_member_payout( - &mut member, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut member, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 10); // (10 * 10 del virtual points / 10 pool points) * 5 pool balance @@ -889,12 +883,9 @@ mod claim_payout { Balances::make_free_balance_be(&default_reward_account(), ed + 0); // When - let payout = Pools::calculate_member_payout( - &mut member, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut member, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 0); @@ -925,12 +916,9 @@ mod claim_payout { Balances::make_free_balance_be(&default_reward_account(), ed + 100); // When - let payout = Pools::calculate_member_payout( - &mut del_10, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 10); // (10 del virtual points / 100 pool points) * 100 pool balance @@ -940,12 +928,9 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 10)); // When - let payout = Pools::calculate_member_payout( - &mut del_40, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut del_40, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 40); // (400 del virtual points / 900 pool points) * 90 pool balance @@ -963,12 +948,9 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 40)); // When - let payout = Pools::calculate_member_payout( - &mut del_50, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut del_50, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 50); // (50 del virtual points / 50 pool points) * 50 pool balance @@ -981,12 +963,9 @@ mod claim_payout { Balances::make_free_balance_be(&default_reward_account(), ed + 50); // When - let payout = Pools::calculate_member_payout( - &mut del_10, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 5); // (500 del virtual points / 5,000 pool points) * 50 pool balance @@ -996,12 +975,9 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 5)); // When - let payout = Pools::calculate_member_payout( - &mut del_40, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut del_40, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 20); // (2,000 del virtual points / 4,500 pool points) * 45 pool balance @@ -1014,12 +990,9 @@ mod claim_payout { assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75); // When - let payout = Pools::calculate_member_payout( - &mut del_50, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut del_50, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 50); // (5,000 del virtual points / 7,5000 pool points) * 75 pool balance @@ -1040,12 +1013,9 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 50)); // When - let payout = Pools::calculate_member_payout( - &mut del_10, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 5); @@ -1059,12 +1029,9 @@ mod claim_payout { assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420); // When - let payout = Pools::calculate_member_payout( - &mut del_10, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 40); @@ -1089,12 +1056,9 @@ mod claim_payout { assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400); // When - let payout = Pools::calculate_member_payout( - &mut del_10, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 2); // (200 del virtual points / 38,000 pool points) * 400 pool balance @@ -1104,12 +1068,9 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 2)); // When - let payout = Pools::calculate_member_payout( - &mut del_40, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut del_40, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 188); // (18,800 del virtual points / 39,800 pool points) * 399 pool balance @@ -1119,12 +1080,9 @@ mod claim_payout { assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 188)); // When - let payout = Pools::calculate_member_payout( - &mut del_50, - &mut bonded_pool, - &mut reward_pool, - ) - .unwrap(); + let payout = + Pools::calculate_member_payout(&mut del_50, &mut bonded_pool, &mut reward_pool) + .unwrap(); // Then assert_eq!(payout, 210); // (21,000 / 21,000) * 210 @@ -1831,49 +1789,43 @@ mod unbond { // depositor can unbond inly up to `MinCreateBond`. #[test] fn depositor_permissioned_partial_unbond() { - ExtBuilder::default() - .ed(1) - .add_members(vec![(100, 100)]) - .build_and_execute(|| { - // given - assert_eq!(MinCreateBond::::get(), 2); - assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); - assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); + ExtBuilder::default().ed(1).add_members(vec![(100, 100)]).build_and_execute(|| { + // given + assert_eq!(MinCreateBond::::get(), 2); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); - // can unbond a bit.. - assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); - assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 7); - assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 3); + // can unbond a bit.. + assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 7); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 3); - // but not less than 2 - assert_noop!( - Pools::unbond(Origin::signed(10), 10, 6), - Error::::NotOnlyPoolMember - ); - }); + // but not less than 2 + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 6), + Error::::NotOnlyPoolMember + ); + }); } // same as above, but the pool is slashed and therefore the depositor cannot partially unbond. #[test] fn depositor_permissioned_partial_unbond_slashed() { - ExtBuilder::default() - .ed(1) - .add_members(vec![(100, 100)]) - .build_and_execute(|| { - // given - assert_eq!(MinCreateBond::::get(), 2); - assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); - assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); + ExtBuilder::default().ed(1).add_members(vec![(100, 100)]).build_and_execute(|| { + // given + assert_eq!(MinCreateBond::::get(), 2); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); - // slash the default pool - StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 5); + // slash the default pool + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 5); - // cannot unbond even 7, because the value of shares is now less. - assert_noop!( - Pools::unbond(Origin::signed(10), 10, 7), - Error::::NotOnlyPoolMember - ); - }); + // cannot unbond even 7, because the value of shares is now less. + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 7), + Error::::NotOnlyPoolMember + ); + }); } } @@ -2547,7 +2499,11 @@ mod create { assert_eq!(Balances::free_balance(&11), 0); assert_eq!( PoolMembers::::get(11).unwrap(), - PoolMember { pool_id: 2, points: StakingMock::minimum_bond(), ..Default::default() } + PoolMember { + pool_id: 2, + points: StakingMock::minimum_bond(), + ..Default::default() + } ); assert_eq!( BondedPool::::get(2).unwrap(), From 200611efa11682495aed6483fb323c3ca1d8949c Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 21 Apr 2022 17:26:54 -0700 Subject: [PATCH 289/299] Get runtime to build with runtime-benchmarks feature --- Cargo.lock | 6 ------ frame/nomination-pools/Cargo.toml | 9 +++++---- .../nomination-pools/benchmarking/Cargo.toml | 10 ++++------ .../nomination-pools/benchmarking/src/lib.rs | 19 +++++++++++++++++++ 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3149d402efc7c..6608060142eee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6207,17 +6207,11 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.16", "pallet-bags-list", - "pallet-balances", "pallet-nomination-pools", "pallet-staking", - "pallet-staking-reward-curve", - "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", "sp-runtime", "sp-staking", "sp-std", diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 5e485e62f21d9..882a6f363a14f 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -23,10 +23,10 @@ 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 = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } [features] @@ -34,10 +34,11 @@ runtime-benchmarks = [] default = ["std"] std = [ "codec/std", - "sp-core/std", - "sp-io/std", "scale-info/std", - "sp-std/std", "frame-support/std", "frame-system/std", + "sp-runtime/std", + "sp-std/std", + "sp-staking/std", + "sp-core/std", ] diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index 8416216d12e94..58158c20cf195 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -31,8 +31,6 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../../pri sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } -log = { version = "0.4.14", default-features = false } - [dev-dependencies] pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } @@ -40,18 +38,18 @@ pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/rew sp-core = { version = "6.0.0", path = "../../../primitives/core" } sp-io = { version = "6.0.0", path = "../../../primitives/io" } - [features] default = ["std"] std = [ "frame-benchmarking/std", + "frame-election-provider-support/std", "frame-support/std", "frame-system/std", - "frame-election-provider-support/std", - "pallet-balances/std", "pallet-bags-list/std", "pallet-staking/std", "pallet-nomination-pools/std", "sp-runtime/std", - "sp-staking/std" + "sp-staking/std", + "sp-std/std", + "pallet-balances/std", ] diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 84d62535b6792..092335d5d3d91 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -1,5 +1,24 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-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. + //! Benchmarks for the nomination pools coupled with the staking and bags list pallets. +#![cfg_attr(not(feature = "std"), no_std)] + #[cfg(test)] mod mock; From ea019ba149e2184ecf76e6f3da8b9d54e0880fce Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 21 Apr 2022 17:29:16 -0700 Subject: [PATCH 290/299] Update Cargo.lock --- Cargo.lock | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 6608060142eee..2ad3f478ba6de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6208,10 +6208,15 @@ dependencies = [ "frame-support", "frame-system", "pallet-bags-list", + "pallet-balances", "pallet-nomination-pools", "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", "parity-scale-codec", "scale-info", + "sp-core", + "sp-io", "sp-runtime", "sp-staking", "sp-std", From f77dfc879d76c05b486b3bcdb2590768fa5bd14f Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 21 Apr 2022 17:49:41 -0700 Subject: [PATCH 291/299] Fix asserts to work in more scenarios --- frame/nomination-pools/benchmarking/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 092335d5d3d91..a6867b560c294 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -247,8 +247,8 @@ frame_benchmarking::benchmarks! { }: bond_extra(Origin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra)) verify { - assert_eq!( - T::StakingInterface::active_stake(&scenario.origin1).unwrap(), + assert!( + T::StakingInterface::active_stake(&scenario.origin1).unwrap() > scenario.dest_weight ); } @@ -267,8 +267,8 @@ frame_benchmarking::benchmarks! { }: bond_extra(Origin::Signed(scenario.creator1.clone()), BondExtra::Rewards) verify { - assert_eq!( - T::StakingInterface::active_stake(&scenario.origin1).unwrap(), + assert!( + T::StakingInterface::active_stake(&scenario.origin1).unwrap() > scenario.dest_weight ); } From 562a1be74a425f207a54474659a4fac6a05a1352 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Thu, 21 Apr 2022 18:10:23 -0700 Subject: [PATCH 292/299] gte not gt --- frame/nomination-pools/benchmarking/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index a6867b560c294..7ae6338e6304a 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -248,7 +248,7 @@ frame_benchmarking::benchmarks! { }: bond_extra(Origin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra)) verify { assert!( - T::StakingInterface::active_stake(&scenario.origin1).unwrap() > + T::StakingInterface::active_stake(&scenario.origin1).unwrap() >= scenario.dest_weight ); } @@ -268,7 +268,7 @@ frame_benchmarking::benchmarks! { }: bond_extra(Origin::Signed(scenario.creator1.clone()), BondExtra::Rewards) verify { assert!( - T::StakingInterface::active_stake(&scenario.origin1).unwrap() > + T::StakingInterface::active_stake(&scenario.origin1).unwrap() >= scenario.dest_weight ); } From 3905a66526208560c366ac17574010987e5713f8 Mon Sep 17 00:00:00 2001 From: Parity Bot Date: Fri, 22 Apr 2022 02:13:21 +0000 Subject: [PATCH 293/299] cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_nomination_pools --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/nomination-pools/src/weights.rs --template=./.maintain/frame-weight-template.hbs --- frame/nomination-pools/src/weights.rs | 355 ++++++++++++++++++-------- 1 file changed, 243 insertions(+), 112 deletions(-) diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index efd45eb6f08d0..4bd291f5276f8 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -1,4 +1,5 @@ // This file is part of Substrate. + // Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 @@ -17,12 +18,13 @@ //! Autogenerated weights for pallet_nomination_pools //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-02-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-04-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -52,55 +54,79 @@ pub trait WeightInfo { fn withdraw_unbonded_update(s: u32, ) -> Weight; fn withdraw_unbonded_kill(s: u32, ) -> Weight; fn create() -> Weight; - fn nominate(n: u32) -> Weight; + fn nominate(n: u32, ) -> Weight; fn set_state() -> Weight; - fn set_metadata(_n: u32) -> Weight; + fn set_metadata(n: u32, ) -> Weight; fn set_configs() -> Weight; } /// Weights for pallet_nomination_pools using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools MinJoinBond (r:1 w:0) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:0) // Storage: System Account (r:2 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + // Storage: NominationPools MaxPoolMembers (r:1 w:0) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) // Storage: Staking Bonded (r:1 w:0) // Storage: Balances Locks (r:1 w:1) // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) - // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn join() -> Weight { - (109_762_000 as Weight) - .saturating_add(T::DbWeight::get().reads(15 as Weight)) - .saturating_add(T::DbWeight::get().writes(11 as Weight)) + (117_870_000 as Weight) + .saturating_add(T::DbWeight::get().reads(18 as Weight)) + .saturating_add(T::DbWeight::get().writes(12 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:2 w:2) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:3 w:3) + // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (109_762_000 as Weight) - .saturating_add(T::DbWeight::get().reads(15 as Weight)) - .saturating_add(T::DbWeight::get().writes(11 as Weight)) + (110_176_000 as Weight) + .saturating_add(T::DbWeight::get().reads(14 as Weight)) + .saturating_add(T::DbWeight::get().writes(13 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:3 w:3) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:2 w:2) + // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (109_762_000 as Weight) - .saturating_add(T::DbWeight::get().reads(15 as Weight)) - .saturating_add(T::DbWeight::get().writes(11 as Weight)) + (122_829_000 as Weight) + .saturating_add(T::DbWeight::get().reads(14 as Weight)) + .saturating_add(T::DbWeight::get().writes(13 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (45_848_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + (50_094_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:2 w:1) - // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Staking Nominators (r:1 w:0) @@ -109,143 +135,208 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListBags (r:2 w:2) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (114_657_000 as Weight) - .saturating_add(T::DbWeight::get().reads(18 as Weight)) - .saturating_add(T::DbWeight::get().writes(13 as Weight)) + (119_288_000 as Weight) + .saturating_add(T::DbWeight::get().reads(19 as Weight)) + .saturating_add(T::DbWeight::get().writes(14 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (33_087_000 as Weight) + (39_986_000 as Weight) // Standard Error: 0 .saturating_add((50_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools SubPoolsStorage (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { - (66_522_000 as Weight) + (76_897_000 as Weight) // Standard Error: 0 - .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(8 as Weight)) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(T::DbWeight::get().reads(9 as Weight)) + .saturating_add(T::DbWeight::get().writes(8 as Weight)) } - fn withdraw_unbonded_kill(s: u32, ) -> Weight { - (66_522_000 as Weight) - // Standard Error: 0 - .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(8 as Weight)) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: Balances Locks (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + // Storage: NominationPools CounterForBondedPools (r:1 w:1) + // Storage: Staking Payee (r:0 w:1) + fn withdraw_unbonded_kill(_s: u32, ) -> Weight { + (135_837_000 as Weight) + .saturating_add(T::DbWeight::get().reads(20 as Weight)) + .saturating_add(T::DbWeight::get().writes(17 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Staking MinNominatorBond (r:1 w:0) // Storage: NominationPools MinCreateBond (r:1 w:0) + // Storage: NominationPools MinJoinBond (r:1 w:0) // Storage: NominationPools MaxPools (r:1 w:0) // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: System ParentHash (r:1 w:0) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) - // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools LastPoolId (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: System Account (r:2 w:2) // Storage: Staking Ledger (r:1 w:1) - // Storage: System Account (r:1 w:1) // Storage: Staking Bonded (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking HistoryDepth (r:1 w:0) // Storage: Balances Locks (r:1 w:1) - // Storage: NominationPools CounterForPoolMembers (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (92_839_000 as Weight) - .saturating_add(T::DbWeight::get().reads(17 as Weight)) - .saturating_add(T::DbWeight::get().writes(11 as Weight)) + (129_265_000 as Weight) + .saturating_add(T::DbWeight::get().reads(23 as Weight)) + .saturating_add(T::DbWeight::get().writes(16 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:0) // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinNominatorBond (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) // Storage: Staking MaxNominatorsCount (r:1 w:0) - // Storage: Staking Validators (r:17 w:0) + // Storage: Staking Validators (r:2 w:0) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListNodes (r:1 w:1) // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) - fn nominate(_n: u32) -> Weight { - (79_587_000 as Weight) - .saturating_add(T::DbWeight::get().reads(28 as Weight)) + fn nominate(n: u32, ) -> Weight { + (45_546_000 as Weight) + // Standard Error: 11_000 + .saturating_add((2_075_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (79_587_000 as Weight) - .saturating_add(T::DbWeight::get().reads(28 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) + (23_256_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) } - fn set_metadata(_n: u32) -> Weight { - (79_587_000 as Weight) - .saturating_add(T::DbWeight::get().reads(28 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: NominationPools Metadata (r:1 w:1) + // Storage: NominationPools CounterForMetadata (r:1 w:1) + fn set_metadata(n: u32, ) -> Weight { + (10_893_000 as Weight) + // Standard Error: 0 + .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) } + // Storage: NominationPools MinJoinBond (r:0 w:1) + // Storage: NominationPools MaxPoolMembers (r:0 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:0 w:1) + // Storage: NominationPools MinCreateBond (r:0 w:1) + // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (79_587_000 as Weight) - .saturating_add(T::DbWeight::get().reads(28 as Weight)) + (2_793_000 as Weight) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } } // For backwards compatibility and tests impl WeightInfo for () { + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools MinJoinBond (r:1 w:0) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:0) // Storage: System Account (r:2 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + // Storage: NominationPools MaxPoolMembers (r:1 w:0) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) // Storage: Staking Bonded (r:1 w:0) // Storage: Balances Locks (r:1 w:1) // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) - // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn join() -> Weight { - (109_762_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(15 as Weight)) - .saturating_add(RocksDbWeight::get().writes(11 as Weight)) - } - fn bond_extra_reward() -> Weight { - (109_762_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(15 as Weight)) - .saturating_add(RocksDbWeight::get().writes(11 as Weight)) + (117_870_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(18 as Weight)) + .saturating_add(RocksDbWeight::get().writes(12 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:2 w:2) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:3 w:3) + // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (109_762_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(15 as Weight)) - .saturating_add(RocksDbWeight::get().writes(11 as Weight)) + (110_176_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(14 as Weight)) + .saturating_add(RocksDbWeight::get().writes(13 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:3 w:3) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:2 w:2) + // Storage: BagsList ListBags (r:2 w:2) + fn bond_extra_reward() -> Weight { + (122_829_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(14 as Weight)) + .saturating_add(RocksDbWeight::get().writes(13 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (45_848_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + (50_094_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:2 w:1) - // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Staking Nominators (r:1 w:0) @@ -254,98 +345,138 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListBags (r:2 w:2) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (114_657_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(18 as Weight)) - .saturating_add(RocksDbWeight::get().writes(13 as Weight)) + (119_288_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(19 as Weight)) + .saturating_add(RocksDbWeight::get().writes(14 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (33_087_000 as Weight) + (39_986_000 as Weight) // Standard Error: 0 .saturating_add((50_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools SubPoolsStorage (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) - fn withdraw_unbonded_update(s: u32) -> Weight { - (66_522_000 as Weight) + fn withdraw_unbonded_update(s: u32, ) -> Weight { + (76_897_000 as Weight) // Standard Error: 0 - .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(8 as Weight)) - .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(RocksDbWeight::get().reads(9 as Weight)) + .saturating_add(RocksDbWeight::get().writes(8 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: Balances Locks (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) - fn withdraw_unbonded_kill(s: u32) -> Weight { - (66_522_000 as Weight) - // Standard Error: 0 - .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(8 as Weight)) - .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + // Storage: NominationPools CounterForBondedPools (r:1 w:1) + // Storage: Staking Payee (r:0 w:1) + fn withdraw_unbonded_kill(_s: u32, ) -> Weight { + (135_837_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(20 as Weight)) + .saturating_add(RocksDbWeight::get().writes(17 as Weight)) } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Staking MinNominatorBond (r:1 w:0) // Storage: NominationPools MinCreateBond (r:1 w:0) + // Storage: NominationPools MinJoinBond (r:1 w:0) // Storage: NominationPools MaxPools (r:1 w:0) // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: System ParentHash (r:1 w:0) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) - // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools LastPoolId (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: System Account (r:2 w:2) // Storage: Staking Ledger (r:1 w:1) - // Storage: System Account (r:1 w:1) // Storage: Staking Bonded (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking HistoryDepth (r:1 w:0) // Storage: Balances Locks (r:1 w:1) - // Storage: NominationPools CounterForPoolMembers (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (92_839_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(17 as Weight)) - .saturating_add(RocksDbWeight::get().writes(11 as Weight)) + (129_265_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(23 as Weight)) + .saturating_add(RocksDbWeight::get().writes(16 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:0) // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinNominatorBond (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) // Storage: Staking MaxNominatorsCount (r:1 w:0) - // Storage: Staking Validators (r:17 w:0) + // Storage: Staking Validators (r:2 w:0) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListNodes (r:1 w:1) // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) - fn nominate(_n: u32) -> Weight { - (79_587_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(28 as Weight)) + fn nominate(n: u32, ) -> Weight { + (45_546_000 as Weight) + // Standard Error: 11_000 + .saturating_add((2_075_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(12 as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (79_587_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(28 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + (23_256_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } - fn set_metadata(_n: u32) -> Weight { - (79_587_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(28 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: NominationPools Metadata (r:1 w:1) + // Storage: NominationPools CounterForMetadata (r:1 w:1) + fn set_metadata(n: u32, ) -> Weight { + (10_893_000 as Weight) + // Standard Error: 0 + .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } + // Storage: NominationPools MinJoinBond (r:0 w:1) + // Storage: NominationPools MaxPoolMembers (r:0 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:0 w:1) + // Storage: NominationPools MinCreateBond (r:0 w:1) + // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (79_587_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(28 as Weight)) + (2_793_000 as Weight) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } } From 33e0eb54fdd7e76e899b963cdae5b967ac2c1db5 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Thu, 21 Apr 2022 19:56:02 -0700 Subject: [PATCH 294/299] Update frame/staking/src/mock.rs --- frame/staking/src/mock.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index a9d74841adcaf..bd2d8cdc32ce9 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -887,25 +887,3 @@ pub(crate) fn staking_events() -> Vec> { pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) { (Balances::free_balance(who), Balances::reserved_balance(who)) } - -// fn do_nominate_bench_setup() -> Vec<> { -// // clean up any existing state. -// clear_validators_and_nominators::(); - -// let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); - -// // setup a worst case list scenario. Note we don't care about the destination position, because -// // we are just doing an insert into the origin position. -// let scenario = ListScenario::::new(origin_weight, true)?; -// let (stash, controller) = create_stash_controller_with_balance::( -// SEED + T::MaxNominations::get() + 1, // make sure the account does not conflict with others -// origin_weight, -// Default::default(), -// ).unwrap(); - -// assert!(!Nominators::::contains_key(&stash)); -// assert!(!T::SortedListProvider::contains(&stash)); - -// let validators = create_validators::(T::MaxNominations::get(), 100).unwrap(); -// return validators -// } From af52ec4f326d6dfaec29eaa7e91cbcb323039e71 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Fri, 22 Apr 2022 12:42:47 -0700 Subject: [PATCH 295/299] Update frame/nomination-pools/src/lib.rs --- frame/nomination-pools/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index b4da4aa93b51a..a2fdff0019049 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -35,7 +35,7 @@ //! * unbonding sub pools: Collection of pools at different phases of the unbonding lifecycle. See //! [`SubPools`] and [`SubPoolsStorage`]. Sub pools are identified via the pools bonded account. //! * members: Accounts that are members of pools. See [`PoolMember`] and [`PoolMembers`]. -//! PoolMembers are identified via their account. +//! Pool members are identified via their account. //! * point: A unit of measure for a members portion of a pool's funds. //! * kick: The act of a pool administrator forcibly ejecting a member. //! From 4d7c98c4a6de73ae18e4868f19d096b96c315dc1 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Fri, 22 Apr 2022 13:05:47 -0700 Subject: [PATCH 296/299] Update frame/staking/src/slashing.rs --- frame/staking/src/slashing.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index d7829a7f2527c..9fc50eaf538f6 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -613,11 +613,6 @@ pub fn do_slash( let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era); if !value.is_zero() { - // TODO: if this happens the UnbondPools that did not get slashed could think - // there are more funds unbonding then there really is, which could lead to attempts - // at over-withdraw when a delegator goes to withdraw unbonded - // to solve this, when the go to withdraw unbonded, we can min the withdraw amount - // with the non locked balance. let (imbalance, missing) = T::Currency::slash(stash, value); slashed_imbalance.subsume(imbalance); From 42ef1d85baa519f638c462c549e3d21a5452d0cc Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Fri, 22 Apr 2022 13:09:02 -0700 Subject: [PATCH 297/299] Apply suggestions from code review --- primitives/staking/src/lib.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 7b118d15cfbdd..7ff0808af0a63 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -30,19 +30,20 @@ pub type SessionIndex = u32; /// Counter for the number of eras that have passed. pub type EraIndex = u32; -/// Trait for a hook for any operations to perform while a staker is being slashed. +/// Trait describing something that implements a hook for any operations to perform when a staker is +/// slashed. pub trait OnStakerSlash { - /// A hook for any operations to perform while a staker is being slashed. + /// A hook for any operations to perform when a staker is slashed. /// /// # Arguments /// /// * `stash` - The stash of the staker whom the slash was applied to. - /// * `slashed_bonded` - The new bonded balance of the staker after the slash was applied. - /// * `slashed_unlocking` - A map from eras that the staker is unbonding in to the new balance - /// after the slash was applied. + /// * `slashed_active` - The new bonded balance of the staker after the slash was applied. + /// * `slashed_unlocking` - A map of slashed eras, and the balance of that unlocking chunk after + /// the slash is applied. Any era not present in the map is not affected at all. fn on_slash( stash: &AccountId, - slashed_bonded: Balance, + slashed_active: Balance, slashed_unlocking: &BTreeMap, ); } From e4c4b28165c6a06eb88293a7bbbbb55ef37f5c56 Mon Sep 17 00:00:00 2001 From: emostov <32168567+emostov@users.noreply.github.com> Date: Fri, 22 Apr 2022 13:11:53 -0700 Subject: [PATCH 298/299] fmt --- frame/nomination-pools/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index a2fdff0019049..bafed9fc2f5b4 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -34,8 +34,8 @@ //! [`RewardPools`]. Reward pools are identified via the pools bonded account. //! * unbonding sub pools: Collection of pools at different phases of the unbonding lifecycle. See //! [`SubPools`] and [`SubPoolsStorage`]. Sub pools are identified via the pools bonded account. -//! * members: Accounts that are members of pools. See [`PoolMember`] and [`PoolMembers`]. -//! Pool members are identified via their account. +//! * members: Accounts that are members of pools. See [`PoolMember`] and [`PoolMembers`]. Pool +//! members are identified via their account. //! * point: A unit of measure for a members portion of a pool's funds. //! * kick: The act of a pool administrator forcibly ejecting a member. //! From 3431cfaaa012459663dd3fad4bb4f46b2e592292 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 26 Apr 2022 20:57:48 +0200 Subject: [PATCH 299/299] Fix some tests --- frame/nomination-pools/src/tests.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 6119b354b8d25..7df922280873b 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -502,7 +502,8 @@ mod join { } #[test] - #[should_panic = "Defensive failure has been triggered!"] + #[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))] + #[cfg_attr(not(debug_assertions), should_panic)] fn join_panics_when_reward_pool_not_found() { ExtBuilder::default().build_and_execute(|| { StakingMock::set_bonded_balance(Pools::create_bonded_account(123), 100); @@ -1603,7 +1604,8 @@ mod unbond { } #[test] - #[should_panic = "Defensive failure has been triggered!"] + #[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))] + #[cfg_attr(not(debug_assertions), should_panic)] fn unbond_errors_correctly() { ExtBuilder::default().build_and_execute(|| { assert_noop!( @@ -1620,7 +1622,8 @@ mod unbond { } #[test] - #[should_panic = "Defensive failure has been triggered!"] + #[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))] + #[cfg_attr(not(debug_assertions), should_panic)] fn unbond_panics_when_reward_pool_not_found() { ExtBuilder::default().build_and_execute(|| { let member = PoolMember { pool_id: 2, points: 10, ..Default::default() };