diff --git a/Cargo.lock b/Cargo.lock index a5e431c7a4aa0..ae0a497867b51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5931,6 +5931,30 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-nomination-pools-test-staking" +version = "1.0.0" +dependencies = [ + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-bags-list", + "pallet-balances", + "pallet-nomination-pools", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", + "sp-tracing", +] + [[package]] name = "pallet-offences" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 7f013c08a9144..9909e6f893877 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,6 +115,7 @@ members = [ "frame/proxy", "frame/nomination-pools", "frame/nomination-pools/benchmarking", + "frame/nomination-pools/test-staking", "frame/randomness-collective-flip", "frame/ranked-collective", "frame/recovery", diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 1c7794fd0cde6..47c3634aa00df 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -23,8 +23,9 @@ use hex_literal::hex; use node_runtime::{ constants::currency::*, wasm_binary_unwrap, AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, Block, CouncilConfig, DemocracyConfig, ElectionsConfig, GrandpaConfig, - ImOnlineConfig, IndicesConfig, MaxNominations, SessionConfig, SessionKeys, SocietyConfig, - StakerStatus, StakingConfig, SudoConfig, SystemConfig, TechnicalCommitteeConfig, + ImOnlineConfig, IndicesConfig, MaxNominations, NominationPoolsConfig, SessionConfig, + SessionKeys, SocietyConfig, StakerStatus, StakingConfig, SudoConfig, SystemConfig, + TechnicalCommitteeConfig, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sc_chain_spec::ChainSpecExtension; @@ -365,7 +366,11 @@ pub fn testnet_genesis( transaction_payment: Default::default(), alliance: Default::default(), alliance_motion: Default::default(), - nomination_pools: Default::default(), + nomination_pools: NominationPoolsConfig { + min_create_bond: 10 * DOLLARS, + min_join_bond: 1 * DOLLARS, + ..Default::default() + }, } } diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 0820125d77f44..be5c38d85552c 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -23,11 +23,11 @@ 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" } log = { version = "0.4.0", default-features = false } [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] @@ -41,6 +41,7 @@ std = [ "frame-system/std", "sp-runtime/std", "sp-std/std", + "sp-io/std", "sp-staking/std", "sp-core/std", "log/std", diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 275b914cda297..4c2c902846a85 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -24,7 +24,7 @@ mod mock; 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_support::{assert_ok, ensure, traits::Get}; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ BalanceOf, BondExtra, BondedPoolInner, BondedPools, ConfigOp, MaxPoolMembers, @@ -48,6 +48,12 @@ pub trait Config: pub struct Pallet(Pools); +fn min_create_bond() -> BalanceOf { + MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()) +} + fn create_funded_user_with_balance( string: &'static str, n: u32, @@ -209,9 +215,7 @@ impl ListScenario { frame_benchmarking::benchmarks! { join { - let origin_weight = pallet_nomination_pools::MinCreateBond::::get() - .max(CurrencyOf::::minimum_balance()) - * 2u32.into(); + let origin_weight = min_create_bond::() * 2u32.into(); // setup the worst case list scenario. let scenario = ListScenario::::new(origin_weight, true)?; @@ -237,9 +241,7 @@ frame_benchmarking::benchmarks! { } bond_extra_transfer { - let origin_weight = pallet_nomination_pools::MinCreateBond::::get() - .max(CurrencyOf::::minimum_balance()) - * 2u32.into(); + let origin_weight = min_create_bond::() * 2u32.into(); let scenario = ListScenario::::new(origin_weight, true)?; let extra = scenario.dest_weight.clone() - origin_weight; @@ -254,9 +256,7 @@ frame_benchmarking::benchmarks! { } bond_extra_reward { - let origin_weight = pallet_nomination_pools::MinCreateBond::::get() - .max(CurrencyOf::::minimum_balance()) - * 2u32.into(); + let origin_weight = min_create_bond::() * 2u32.into(); let scenario = ListScenario::::new(origin_weight, true)?; let extra = (scenario.dest_weight.clone() - origin_weight).max(CurrencyOf::::minimum_balance()); @@ -274,7 +274,7 @@ frame_benchmarking::benchmarks! { } claim_payout { - let origin_weight = pallet_nomination_pools::MinCreateBond::::get().max(CurrencyOf::::minimum_balance()) * 2u32.into(); + let origin_weight = min_create_bond::() * 2u32.into(); let ed = CurrencyOf::::minimum_balance(); let (depositor, pool_account) = create_pool_account::(0, origin_weight); let reward_account = Pools::::create_reward_account(1); @@ -304,9 +304,7 @@ frame_benchmarking::benchmarks! { unbond { // The weight the nominator will start at. The value used here is expected to be // significantly higher than the first position in a list (e.g. the first bag threshold). - let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) - .map_err(|_| "balance expected to be a u128") - .unwrap(); + let origin_weight = min_create_bond::() * 200u32.into(); let scenario = ListScenario::::new(origin_weight, false)?; let amount = origin_weight - scenario.dest_weight.clone(); @@ -336,9 +334,7 @@ frame_benchmarking::benchmarks! { pool_withdraw_unbonded { let s in 0 .. MAX_SPANS; - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); + let min_create_bond = min_create_bond::(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Add a new member @@ -380,9 +376,7 @@ frame_benchmarking::benchmarks! { withdraw_unbonded_update { let s in 0 .. MAX_SPANS; - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); + let min_create_bond = min_create_bond::(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Add a new member @@ -427,10 +421,7 @@ frame_benchmarking::benchmarks! { withdraw_unbonded_kill { let s in 0 .. MAX_SPANS; - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); - + let min_create_bond = min_create_bond::(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // We set the pool to the destroying state so the depositor can leave @@ -494,9 +485,7 @@ frame_benchmarking::benchmarks! { } create { - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); + let min_create_bond = min_create_bond::(); let depositor: T::AccountId = account("depositor", USER_SEED, 0); // Give the depositor some balance to bond @@ -542,9 +531,7 @@ frame_benchmarking::benchmarks! { let n in 1 .. T::MaxNominations::get(); // Create a pool - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); + let min_create_bond = min_create_bond::() * 2u32.into(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Create some accounts to nominate. For the sake of benchmarking they don't need to be @@ -581,9 +568,7 @@ frame_benchmarking::benchmarks! { set_state { // Create a pool - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); + let min_create_bond = min_create_bond::(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); BondedPools::::mutate(&1, |maybe_pool| { // Force the pool into an invalid state @@ -601,10 +586,7 @@ frame_benchmarking::benchmarks! { let n in 1 .. ::MaxMetadataLen::get(); // 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); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond::() * 2u32.into()); // Create metadata of the max possible size let metadata: Vec = (0..n).map(|_| 42).collect(); @@ -633,7 +615,7 @@ frame_benchmarking::benchmarks! { update_roles { let first_id = pallet_nomination_pools::LastPoolId::::get() + 1; - let (root, _) = create_pool_account::(0, CurrencyOf::::minimum_balance() * 2u32.into()); + let (root, _) = create_pool_account::(0, min_create_bond::() * 2u32.into()); let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED); }:_( Origin::Signed(root.clone()), @@ -653,6 +635,24 @@ frame_benchmarking::benchmarks! { ) } + chill { + // Create a pool + let (depositor, pool_account) = create_pool_account::(0, min_create_bond::() * 2u32.into()); + + // Nominate with the pool. + let validators: Vec<_> = (0..T::MaxNominations::get()) + .map(|i| account("stash", USER_SEED, i)) + .collect(); + + assert_ok!(Pools::::nominate(Origin::Signed(depositor.clone()).into(), 1, validators)); + assert!(T::StakingInterface::nominations(Pools::::create_bonded_account(1)).is_some()); + + whitelist_account!(depositor); + }:_(Origin::Signed(depositor.clone()), 1) + verify { + assert!(T::StakingInterface::nominations(Pools::::create_bonded_account(1)).is_none()); + } + impl_benchmark_test_suite!( Pallet, crate::mock::new_test_ext(), diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index dc40a6a7d41fd..19155a51405b0 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -447,21 +447,27 @@ impl PoolMember { .fold(BalanceOf::::zero(), |acc, (_, v)| acc.saturating_add(*v)) } - /// Try and unbond `points` from self, with the given target unbonding era. + /// Try and unbond `points_dissolved` from self, and in return mint `points_issued` into the + /// corresponding `era`'s unlock schedule. + /// + /// In the absence of slashing, these two points are always the same. In the presence of + /// slashing, the value of points in different pools varies. /// /// Returns `Ok(())` and updates `unbonding_eras` and `points` if success, `Err(_)` otherwise. fn try_unbond( &mut self, - points: BalanceOf, + points_dissolved: BalanceOf, + points_issued: BalanceOf, unbonding_era: EraIndex, ) -> Result<(), Error> { - if let Some(new_points) = self.points.checked_sub(&points) { + if let Some(new_points) = self.points.checked_sub(&points_dissolved) { match self.unbonding_eras.get_mut(&unbonding_era) { Some(already_unbonding_points) => - *already_unbonding_points = already_unbonding_points.saturating_add(points), + *already_unbonding_points = + already_unbonding_points.saturating_add(points_issued), None => self .unbonding_eras - .try_insert(unbonding_era, points) + .try_insert(unbonding_era, points_issued) .map(|old| { if old.is_some() { defensive!("value checked to not exist in the map; qed"); @@ -721,9 +727,13 @@ impl BondedPool { } fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf) -> bool { - // 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 + // we need to ensure that `self.member_counter == 1` as well, because the depositor's + // initial `MinCreateBond` (or more) is what guarantees that the ledger of the pool does not + // get killed in the staking system, and that it does not fall below `MinimumNominatorBond`, + // which could prevent other non-depositor members from fully leaving. Thus, all members + // must withdraw, then depositor can unbond, and finally withdraw after waiting another + // cycle. + self.is_destroying() && self.points == alleged_depositor_points && self.member_counter == 1 } /// Whether or not the pool is ok to be in `PoolSate::Open`. If this returns an `Err`, then the @@ -984,10 +994,14 @@ impl UnbondPool { 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.balance_to_point(new_funds)); + /// Issue the equivalent points of `new_funds` into self. + /// + /// Returns the actual amounts of points issued. + fn issue(&mut self, new_funds: BalanceOf) -> BalanceOf { + let new_points = self.balance_to_point(new_funds); + self.points = self.points.saturating_add(new_points); self.balance = self.balance.saturating_add(new_funds); + new_points } /// Dissolve some points from the unbonding pool, reducing the balance of the pool @@ -1150,6 +1164,9 @@ pub mod pallet { /// /// This is the amount that the depositor must put as their initial stake in the pool, as an /// indication of "skin in the game". + /// + /// This is the value that will always exist in the staking ledger of the pool bonded account + /// while all other accounts leave. #[pallet::storage] pub type MinCreateBond = StorageValue<_, BalanceOf, ValueQuery>; @@ -1256,9 +1273,34 @@ pub mod pallet { /// 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 }, + /// + /// - `balance` is the corresponding balance of the number of points that has been + /// requested to be unbonded (the argument of the `unbond` transaction) from the bonded + /// pool. + /// - `points` is the number of points that are issued as a result of `balance` being + /// dissolved into the corresponding unbonding pool. + /// + /// In the absence of slashing, these values will match. In the presence of slashing, the + /// number of points that are issued in the unbonding pool will be less than the amount + /// requested to be unbonded. + Unbonded { + member: T::AccountId, + pool_id: PoolId, + balance: BalanceOf, + points: BalanceOf, + }, /// A member has withdrawn from their pool. - Withdrawn { member: T::AccountId, pool_id: PoolId, amount: BalanceOf }, + /// + /// The given number of `points` have been dissolved in return of `balance`. + /// + /// Similar to `Unbonded` event, in the absence of slashing, the ratio of point to balance + /// will be 1. + Withdrawn { + member: T::AccountId, + pool_id: PoolId, + balance: BalanceOf, + points: BalanceOf, + }, /// A pool has been destroyed. Destroyed { pool_id: PoolId }, /// The state of a pool has changed @@ -1274,6 +1316,10 @@ pub mod pallet { state_toggler: Option, nominator: Option, }, + /// The active balance of pool `pool_id` has been slashed to `balance`. + PoolSlashed { pool_id: PoolId, balance: BalanceOf }, + /// The unbond pool at `era` of pool `pool_id` has been slashed to `balance`. + UnbondingPoolSlashed { pool_id: PoolId, era: EraIndex, balance: BalanceOf }, } #[pallet::error] @@ -1290,10 +1336,6 @@ pub mod pallet { /// 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. - InsufficientBond, - /// The member is already unbonding in this era. - AlreadyUnbonding, /// The member is fully unbonded (and thus cannot access the bonded and reward pool /// anymore to, for example, collect rewards). FullyUnbonding, @@ -1346,6 +1388,9 @@ pub mod pallet { RewardPoolNotFound, /// A sub pool does not exist. SubPoolsNotFound, + /// The bonded account should only be killed by the staking system when the depositor is + /// withdrawing + BondedStashKilledPrematurely, } impl From for Error { @@ -1477,7 +1522,7 @@ pub mod pallet { /// 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. + /// rewards would be forfeited. /// /// Under certain conditions, this call can be dispatched permissionlessly (i.e. by any /// account). @@ -1528,9 +1573,6 @@ 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 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)?; @@ -1553,17 +1595,21 @@ pub mod pallet { })?; } - sub_pools + let points_unbonded = sub_pools .with_era .get_mut(&unbond_era) // The above check ensures the pool exists. .defensive_ok_or::>(DefensiveError::PoolNotFound.into())? .issue(unbonding_balance); + // Try and unbond in the member map. + member.try_unbond(unbonding_points, points_unbonded, unbond_era)?; + Self::deposit_event(Event::::Unbonded { member: member_account.clone(), pool_id: member.pool_id, - amount: unbonding_balance, + points: points_unbonded, + balance: unbonding_balance, }); // Now that we know everything has worked write the items to storage. @@ -1644,14 +1690,23 @@ pub mod pallet { // Before calculate the `balance_to_unbond`, with call withdraw unbonded to ensure the // `transferrable_balance` is correct. - T::StakingInterface::withdraw_unbonded( + let stash_killed = T::StakingInterface::withdraw_unbonded( bonded_pool.bonded_account(), num_slashing_spans, )?; + // defensive-only: the depositor puts enough funds into the stash so that it will only + // be destroyed when they are leaving. + ensure!( + !stash_killed || caller == bonded_pool.roles.depositor, + Error::::Defensive(DefensiveError::BondedStashKilledPrematurely) + ); + + let mut sum_unlocked_points: BalanceOf = Zero::zero(); let balance_to_unbond = withdrawn_points .iter() .fold(BalanceOf::::zero(), |accumulator, (era, unlocked_points)| { + sum_unlocked_points = sum_unlocked_points.saturating_add(*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() { @@ -1684,7 +1739,8 @@ pub mod pallet { Self::deposit_event(Event::::Withdrawn { member: member_account.clone(), pool_id: member.pool_id, - amount: balance_to_unbond, + points: sum_unlocked_points, + balance: balance_to_unbond, }); let post_info_weight = if member.total_points().is_zero() { @@ -1811,6 +1867,13 @@ pub mod pallet { Ok(()) } + /// Nominate on behalf of the pool. + /// + /// The dispatch origin of this call must be signed by the pool nominator or the pool + /// root role. + /// + /// This directly forward the call to the staking pallet, on behalf of the pool bonded + /// account. #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))] pub fn nominate( origin: OriginFor, @@ -1820,10 +1883,13 @@ pub mod pallet { let who = ensure_signed(origin)?; let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); - T::StakingInterface::nominate(bonded_pool.bonded_account(), validators)?; - Ok(()) + T::StakingInterface::nominate(bonded_pool.bonded_account(), validators) } + /// Set a new state for the pool. + /// + /// The dispatch origin of this call must be signed by the state toggler, or the root role + /// of the pool. #[pallet::weight(T::WeightInfo::set_state())] pub fn set_state( origin: OriginFor, @@ -1850,6 +1916,10 @@ pub mod pallet { Ok(()) } + /// Set a new metadata for the pool. + /// + /// The dispatch origin of this call must be signed by the state toggler, or the root role + /// of the pool. #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))] pub fn set_metadata( origin: OriginFor, @@ -1961,6 +2031,21 @@ pub mod pallet { bonded_pool.put(); Ok(()) } + + /// Chill on behalf of the pool. + /// + /// The dispatch origin of this call must be signed by the pool nominator or the pool + /// root role, same as [`Pallet::nominate`]. + /// + /// This directly forward the call to the staking pallet, on behalf of the pool bonded + /// account. + #[pallet::weight(T::WeightInfo::chill())] + pub fn chill(origin: OriginFor, pool_id: PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; + let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); + T::StakingInterface::chill(bonded_pool.bonded_account()) + } } #[pallet::hooks] @@ -2175,6 +2260,7 @@ impl Pallet { if reward_pool.total_earnings == BalanceOf::::max_value() { bonded_pool.set_state(PoolState::Destroying); }; + 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); @@ -2355,19 +2441,26 @@ impl OnStakerSlash> for Pallet { pool_account: &T::AccountId, // Bonded balance is always read directly from staking, therefore we need not update // anything here. - _slashed_bonded: BalanceOf, + slashed_bonded: BalanceOf, slashed_unlocking: &BTreeMap>, ) { - if let Some(pool_id) = ReversePoolIdLookup::::get(pool_account).defensive() { + 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 + pool.balance = *slashed_balance; + Self::deposit_event(Event::::UnbondingPoolSlashed { + era: *era, + pool_id, + balance: *slashed_balance, + }); } } + + Self::deposit_event(Event::::PoolSlashed { pool_id, balance: slashed_bonded }); SubPoolsStorage::::insert(pool_id, sub_pools); } } diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 1dadd7e00c528..a3020e5add044 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -2,7 +2,6 @@ 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; @@ -20,17 +19,19 @@ pub fn default_reward_account() -> AccountId { parameter_types! { pub static CurrentEra: EraIndex = 0; pub static BondingDuration: EraIndex = 3; - static BondedBalanceMap: HashMap = Default::default(); - static UnbondingBalanceMap: HashMap = Default::default(); + pub storage BondedBalanceMap: BTreeMap = Default::default(); + pub storage UnbondingBalanceMap: BTreeMap = Default::default(); #[derive(Clone, PartialEq)] pub static MaxUnbonding: u32 = 8; - pub static Nominations: Vec = vec![]; + pub storage Nominations: Option> = None; } 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, bonded)); + let mut x = BondedBalanceMap::get(); + x.insert(who, bonded); + BondedBalanceMap::set(&x) } } @@ -66,22 +67,33 @@ impl sp_staking::StakingInterface for StakingMock { } fn bond_extra(who: Self::AccountId, extra: Self::Balance) -> DispatchResult { - BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(&who).unwrap() += extra); + let mut x = BondedBalanceMap::get(); + x.get_mut(&who).map(|v| *v += extra); + BondedBalanceMap::set(&x); 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); + let mut x = BondedBalanceMap::get(); + *x.get_mut(&who).unwrap() = x.get_mut(&who).unwrap().saturating_sub(amount); + BondedBalanceMap::set(&x); + let mut y = UnbondingBalanceMap::get(); + *y.entry(who).or_insert(Self::Balance::zero()) += amount; + UnbondingBalanceMap::set(&y); Ok(()) } - fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result { + fn chill(_: Self::AccountId) -> sp_runtime::DispatchResult { + Ok(()) + } + + fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result { // 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)); + let mut x = UnbondingBalanceMap::get(); + x.remove(&who); + UnbondingBalanceMap::set(&x); - Ok(100) + Ok(UnbondingBalanceMap::get().is_empty() && BondedBalanceMap::get().is_empty()) } fn bond( @@ -95,9 +107,13 @@ impl sp_staking::StakingInterface for StakingMock { } fn nominate(_: Self::AccountId, nominations: Vec) -> DispatchResult { - Nominations::set(nominations); + Nominations::set(&Some(nominations)); Ok(()) } + + fn nominations(_: Self::AccountId) -> Option> { + Nominations::get() + } } impl frame_system::Config for Runtime { @@ -162,7 +178,6 @@ parameter_types! { pub static MaxMetadataLen: u32 = 2; pub static CheckLevel: u8 = 255; pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); - pub const MinPointsToBalance: u32 = 10; } impl pools::Config for Runtime { type Event = Event; @@ -175,7 +190,7 @@ impl pools::Config for Runtime { type PalletId = PoolsPalletId; type MaxMetadataLen = MaxMetadataLen; type MaxUnbonding = MaxUnbonding; - type MinPointsToBalance = MinPointsToBalance; + type MinPointsToBalance = frame_support::traits::ConstU32<10>; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -265,7 +280,8 @@ pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) -> Result<(), } parameter_types! { - static ObservedEvents: usize = 0; + static PoolsEvents: usize = 0; + static BalancesEvents: usize = 0; } /// All events of this pallet. @@ -275,8 +291,20 @@ pub(crate) fn pool_events_since_last_call() -> Vec> { .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()); + let already_seen = PoolsEvents::get(); + PoolsEvents::set(events.len()); + events.into_iter().skip(already_seen).collect() +} + +/// All events of the `Balances` pallet. +pub(crate) fn balances_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::Balances(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = BalancesEvents::get(); + BalancesEvents::set(events.len()); events.into_iter().skip(already_seen).collect() } diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 93218c78c83fc..97104423c5910 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -18,9 +18,10 @@ use super::*; use crate::{mock::*, Event}; use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, bounded_btree_map, + assert_err, assert_noop, assert_ok, assert_storage_noop, bounded_btree_map, storage::{with_transaction, TransactionOutcome}, }; +use pallet_balances::Event as BEvent; use sp_runtime::traits::Dispatchable; macro_rules! unbonding_pools_with_era { @@ -65,7 +66,7 @@ fn test_setup_works() { ); assert_eq!( RewardPools::::get(last_pool).unwrap(), - RewardPool:: { balance: 0, points: 0.into(), total_earnings: 0 } + RewardPool:: { balance: 0, points: 0u32.into(), total_earnings: 0 } ); assert_eq!( PoolMembers::::get(10).unwrap(), @@ -80,7 +81,7 @@ fn test_setup_works() { assert_eq!(StakingMock::total_stake(&bonded_account).unwrap(), 10); // but not nominating yet. - assert!(Nominations::get().is_empty()); + assert!(Nominations::get().is_none()); // reward account should have an initial ED in it. assert_eq!(Balances::free_balance(&reward_account), Balances::minimum_balance()); @@ -90,101 +91,106 @@ fn test_setup_works() { mod bonded_pool { use super::*; #[test] - fn points_to_issue_works() { - let mut bonded_pool = BondedPool:: { - id: 123123, - inner: BondedPoolInner { - state: PoolState::Open, - points: 100, - member_counter: 1, - roles: DEFAULT_ROLES, - }, - }; + fn balance_to_point_works() { + ExtBuilder::default().build_and_execute(|| { + let mut bonded_pool = BondedPool:: { + id: 123123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + }; - // 1 points : 1 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); - 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.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.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.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.balance_to_point(10), 10); - - // 10 points : 3 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 30); - 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.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.balance_to_point(90), 40); - } + // 1 points : 1 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + assert_eq!(bonded_pool.balance_to_point(10), 10); + assert_eq!(bonded_pool.balance_to_point(0), 0); - #[test] - fn balance_to_unbond_works() { - // 1 balance : 1 points ratio - let mut bonded_pool = BondedPool:: { - id: 123123, - inner: BondedPoolInner { - state: PoolState::Open, - points: 100, - member_counter: 1, - roles: DEFAULT_ROLES, - }, - }; + // 2 points : 1 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 50); + assert_eq!(bonded_pool.balance_to_point(10), 20); - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); - assert_eq!(bonded_pool.points_to_balance(10), 10); - assert_eq!(bonded_pool.points_to_balance(0), 0); + // 1 points : 2 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 50; + assert_eq!(bonded_pool.balance_to_point(10), 5); - // 2 balance : 1 points ratio - bonded_pool.points = 50; - assert_eq!(bonded_pool.points_to_balance(10), 20); + // 100 points : 0 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); + bonded_pool.points = 100; + assert_eq!(bonded_pool.balance_to_point(10), 100 * 10); - // 100 balance : 0 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); - bonded_pool.points = 0; - assert_eq!(bonded_pool.points_to_balance(10), 0); + // 0 points : 100 balance + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 0; + assert_eq!(bonded_pool.balance_to_point(10), 10); - // 0 balance : 100 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); - bonded_pool.points = 100; - assert_eq!(bonded_pool.points_to_balance(10), 0); + // 10 points : 3 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 30); + bonded_pool.points = 100; + assert_eq!(bonded_pool.balance_to_point(10), 33); - // 10 balance : 3 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); - bonded_pool.points = 30; - assert_eq!(bonded_pool.points_to_balance(10), 33); + // 2 points : 3 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 300); + bonded_pool.points = 200; + assert_eq!(bonded_pool.balance_to_point(10), 6); - // 2 balance : 3 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 200); - bonded_pool.points = 300; - assert_eq!(bonded_pool.points_to_balance(10), 6); + // 4 points : 9 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 900); + bonded_pool.points = 400; + assert_eq!(bonded_pool.balance_to_point(90), 40); + }) + } - // 4 balance : 9 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 400); - bonded_pool.points = 900; - assert_eq!(bonded_pool.points_to_balance(90), 40); + #[test] + fn points_to_balance_works() { + ExtBuilder::default().build_and_execute(|| { + // 1 balance : 1 points ratio + let mut bonded_pool = BondedPool:: { + id: 123123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + }; + + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + 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.points_to_balance(10), 20); + + // 100 balance : 0 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 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.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.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.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.points_to_balance(90), 40); + }) } #[test] @@ -200,7 +206,8 @@ mod bonded_pool { }, }; - let min_points_to_balance: u128 = MinPointsToBalance::get().into(); + let min_points_to_balance: u128 = + <::MinPointsToBalance as Get>::get().into(); // Simulate a 100% slashed pool StakingMock::set_bonded_balance(pool.bonded_account(), 0); @@ -267,38 +274,40 @@ mod unbond_pool { #[test] fn points_to_issue_works() { - // 1 points : 1 balance ratio - let unbond_pool = UnbondPool:: { points: 100, balance: 100 }; - 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.balance_to_point(10), 20); - - // 1 points : 2 balance ratio - let unbond_pool = UnbondPool:: { points: 50, balance: 100 }; - 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.balance_to_point(10), 100 * 10); - - // 0 points : 100 balance - let unbond_pool = UnbondPool:: { points: 0, balance: 100 }; - 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.balance_to_point(10), 33); - - // 2 points : 3 balance ratio - let unbond_pool = UnbondPool:: { points: 200, balance: 300 }; - 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.balance_to_point(90), 40); + ExtBuilder::default().build_and_execute(|| { + // 1 points : 1 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 100 }; + 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.balance_to_point(10), 20); + + // 1 points : 2 balance ratio + let unbond_pool = UnbondPool:: { points: 50, balance: 100 }; + 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.balance_to_point(10), 100 * 10); + + // 0 points : 100 balance + let unbond_pool = UnbondPool:: { points: 0, balance: 100 }; + 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.balance_to_point(10), 33); + + // 2 points : 3 balance ratio + let unbond_pool = UnbondPool:: { points: 200, balance: 300 }; + 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.balance_to_point(90), 40); + }) } #[test] @@ -516,7 +525,8 @@ mod join { ); // Force the points:balance ratio to `MinPointsToBalance` (100/10) - let min_points_to_balance: u128 = MinPointsToBalance::get().into(); + let min_points_to_balance: u128 = + <::MinPointsToBalance as Get>::get().into(); StakingMock::set_bonded_balance( Pools::create_bonded_account(123), @@ -972,7 +982,7 @@ mod claim_payout { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 11, pool_id: 1, bonded: 11, joined: true }, - Event::Unbonded { member: 11, pool_id: 1, amount: 11 } + Event::Unbonded { member: 11, pool_id: 1, points: 11, balance: 11 } ] ); }); @@ -1614,14 +1624,14 @@ mod unbond { Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, Event::PaidOut { member: 40, pool_id: 1, payout: 40 }, - Event::Unbonded { member: 40, pool_id: 1, amount: 6 } + Event::Unbonded { member: 40, pool_id: 1, points: 6, balance: 6 } ] ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 94); assert_eq!( PoolMembers::::get(40).unwrap().unbonding_eras, - member_unbonding_eras!(0 + 3 => 40) + member_unbonding_eras!(0 + 3 => 6) ); assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding @@ -1649,24 +1659,27 @@ mod unbond { assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 2); assert_eq!( PoolMembers::::get(550).unwrap().unbonding_eras, - member_unbonding_eras!(0 + 3 => 550) + member_unbonding_eras!(0 + 3 => 92) ); assert_eq!(Balances::free_balance(&550), 550 + 550); assert_eq!( pool_events_since_last_call(), vec![ Event::PaidOut { member: 550, pool_id: 1, payout: 550 }, - Event::Unbonded { member: 550, pool_id: 1, amount: 92 } + Event::Unbonded { member: 550, pool_id: 1, points: 92, balance: 92 } ] ); // When + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 40, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 550, 0)); assert_ok!(fully_unbond_permissioned(10)); // Then assert_eq!( SubPoolsStorage::::get(1).unwrap().with_era, - unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 100, balance: 100 }} + unbonding_pools_with_era! { 6 => UnbondPool { points: 2, balance: 2 }} ); assert_eq!( BondedPool::::get(1).unwrap(), @@ -1675,22 +1688,23 @@ mod unbond { inner: BondedPoolInner { state: PoolState::Destroying, points: 0, - member_counter: 3, + member_counter: 1, roles: DEFAULT_ROLES } } ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); - assert_eq!( - PoolMembers::::get(550).unwrap().unbonding_eras, - member_unbonding_eras!(0 + 3 => 550) - ); - assert_eq!(Balances::free_balance(&550), 550 + 550); + + assert_eq!(Balances::free_balance(&550), 550 + 550 + 92); assert_eq!( pool_events_since_last_call(), vec![ + Event::Withdrawn { member: 40, pool_id: 1, points: 6, balance: 6 }, + Event::MemberRemoved { pool_id: 1, member: 40 }, + Event::Withdrawn { member: 550, pool_id: 1, points: 92, balance: 92 }, + Event::MemberRemoved { pool_id: 1, member: 550 }, Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 2 } + Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2 } ] ); }); @@ -1736,7 +1750,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, amount: 10 } + Event::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10 } ] ); }); @@ -1771,7 +1785,7 @@ mod unbond { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, + Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100 }, ] ); @@ -1780,7 +1794,7 @@ mod unbond { assert_eq!( pool_events_since_last_call(), - vec![Event::Unbonded { member: 200, pool_id: 1, amount: 200 }] + vec![Event::Unbonded { member: 200, pool_id: 1, points: 200, balance: 200 }] ); assert_eq!( @@ -1806,8 +1820,7 @@ mod unbond { } ); assert_eq!( - UNBONDING_BALANCE_MAP - .with(|m| *m.borrow_mut().get(&default_bonded_account()).unwrap()), + *UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(), 100 + 200 ); }); @@ -1849,7 +1862,7 @@ mod unbond { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 100 } + Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100 } ] ); @@ -1871,36 +1884,37 @@ mod unbond { // Given the pools is destroying unsafe_set_state(1, PoolState::Destroying).unwrap(); - // The depositor can be unbonded by anyone. - assert_ok!(Pools::fully_unbond(Origin::signed(420), 10)); - - assert_eq!( - pool_events_since_last_call(), - vec![Event::Unbonded { member: 10, pool_id: 1, amount: 10 }] + // The depositor cannot be unbonded yet. + assert_noop!( + Pools::fully_unbond(Origin::signed(420), 10), + Error::::DoesNotHavePermission, ); + // but when everyone is unbonded it can.. + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 100, 0)); + // still permissionless unbond must be full. assert_noop!( Pools::unbond(Origin::signed(420), 10, 5), Error::::PartialUnbondNotAllowedPermissionlessly, ); + // but full unbond works. + assert_ok!(Pools::fully_unbond(Origin::signed(420), 10)); + assert_eq!(BondedPools::::get(1).unwrap().points, 0); assert_eq!( SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: Default::default(), with_era: unbonding_pools_with_era! { - 0 + 3 => UnbondPool { points: 110, balance: 110 } + 3 + 3 => UnbondPool { points: 10, balance: 10 } } } ); 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()), - 110 - ); + assert_eq!(*UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(), 10); }); } @@ -1988,7 +2002,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, amount: 1 } + Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 } ] ); @@ -2014,7 +2028,7 @@ mod unbond { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Unbonded { member: 10, pool_id: 1, amount: 5 }] + vec![Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5 }] ); // when: casual further unbond, next era. @@ -2041,12 +2055,16 @@ mod unbond { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Unbonded { member: 10, pool_id: 1, amount: 1 }] + vec![Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 }] ); // when: unbonding more than our active: error - assert_noop!( - Pools::unbond(Origin::signed(10), 10, 5), + assert_err!( + frame_support::storage::in_storage_layer(|| Pools::unbond( + Origin::signed(10), + 10, + 5 + )), Error::::NotEnoughPointsToUnbond ); // instead: @@ -2072,7 +2090,7 @@ mod unbond { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Unbonded { member: 10, pool_id: 1, amount: 3 }] + vec![Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 }] ); }); } @@ -2095,8 +2113,12 @@ mod unbond { // when CurrentEra::set(2); - assert_noop!( - Pools::unbond(Origin::signed(10), 10, 4), + assert_err!( + frame_support::storage::in_storage_layer(|| Pools::unbond( + Origin::signed(10), + 10, + 4 + )), Error::::MaxUnbondingLimit ); @@ -2112,9 +2134,9 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, amount: 2 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 3 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 1 } + Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2 }, + Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 }, + Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 } ] ); }) @@ -2145,7 +2167,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, amount: 3 } + Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 } ] ); }); @@ -2201,7 +2223,7 @@ mod unbond { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, // exactly equal to ed, all that can be claimed. Event::PaidOut { member: 10, pool_id: 1, payout: 5 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 2 } + Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2 } ] ); @@ -2217,7 +2239,7 @@ mod unbond { vec![ // exactly equal to ed, all that can be claimed. Event::PaidOut { member: 10, pool_id: 1, payout: 5 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 3 } + Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 } ] ); @@ -2232,7 +2254,7 @@ mod unbond { pool_events_since_last_call(), vec![ Event::PaidOut { member: 10, pool_id: 1, payout: 5 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 5 } + Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5 } ] ); @@ -2277,6 +2299,9 @@ mod withdraw_unbonded { ExtBuilder::default() .add_members(vec![(40, 40), (550, 550)]) .build_and_execute(|| { + // reduce the noise a bit. + let _ = balances_events_since_last_call(); + // Given assert_eq!(StakingMock::bonding_duration(), 3); assert_ok!(Pools::fully_unbond(Origin::signed(550), 550)); @@ -2285,23 +2310,30 @@ mod withdraw_unbonded { 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::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(); + let unbond_pool = sub_pools.with_era.get_mut(&3).unwrap(); // Sanity check - assert_eq!(*unbond_pool, UnbondPool { points: 10, balance: 10 }); + assert_eq!(*unbond_pool, UnbondPool { points: 550 + 40, balance: 550 + 40 }); // Simulate a slash to the pool with_era(current_era), decreasing the balance by // half - unbond_pool.balance = 5; - 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(&default_bonded_account()).unwrap() -= 5); - Balances::make_free_balance_be(&default_bonded_account(), 595); + { + unbond_pool.balance /= 2; // 295 + SubPoolsStorage::::insert(1, sub_pools); + // Update the equivalent of the unbonding chunks for the `StakingMock` + let mut x = UnbondingBalanceMap::get(); + *x.get_mut(&default_bonded_account()).unwrap() /= 5; + UnbondingBalanceMap::set(&x); + Balances::make_free_balance_be( + &default_bonded_account(), + Balances::free_balance(&default_bonded_account()) / 2, // 300 + ); + StakingMock::set_bonded_balance( + default_bonded_account(), + StakingMock::active_stake(&default_bonded_account()).unwrap() / 2, + ); + }; // Advance the current_era to ensure all `with_era` pools will be merged into // `no_era` pool @@ -2311,27 +2343,56 @@ mod withdraw_unbonded { // Simulate some other call to unbond that would merge `with_era` pools into // `no_era` let sub_pools = - SubPoolsStorage::::get(1).unwrap().maybe_merge_pools(current_era + 3); + SubPoolsStorage::::get(1).unwrap().maybe_merge_pools(current_era); SubPoolsStorage::::insert(1, sub_pools); + assert_eq!( SubPoolsStorage::::get(1).unwrap(), SubPools { - no_era: UnbondPool { points: 550 + 40 + 10, balance: 550 + 40 + 5 }, + no_era: UnbondPool { points: 550 + 40, balance: 275 + 20 }, with_era: Default::default() } ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, + Event::Unbonded { member: 550, pool_id: 1, points: 550, balance: 550 }, + Event::Unbonded { member: 40, pool_id: 1, points: 40, balance: 40 }, + ] + ); + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::BalanceSet { + who: default_bonded_account(), + free: 300, + reserved: 0 + }] + ); + // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 550, 0)); // Then assert_eq!( SubPoolsStorage::::get(1).unwrap().no_era, - UnbondPool { points: 40 + 10, balance: 40 + 5 + 5 } + UnbondPool { points: 40, balance: 20 } + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 550, pool_id: 1, balance: 275, points: 550 }, + Event::MemberRemoved { pool_id: 1, member: 550 } + ] + ); + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 }] ); - assert_eq!(Balances::free_balance(&550), 550 + 545); - assert_eq!(Balances::free_balance(&default_bonded_account()), 50); - assert!(!PoolMembers::::contains_key(550)); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 40, 0)); @@ -2339,128 +2400,167 @@ mod withdraw_unbonded { // Then assert_eq!( SubPoolsStorage::::get(1).unwrap().no_era, - UnbondPool { points: 10, balance: 10 } + UnbondPool { points: 0, balance: 0 } ); - assert_eq!(Balances::free_balance(&40), 40 + 40); - assert_eq!(Balances::free_balance(&default_bonded_account()), 50 - 40); assert!(!PoolMembers::::contains_key(40)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 40 }, + Event::MemberRemoved { pool_id: 1, member: 40 } + ] + ); + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 }] + ); - // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + // now, finally, the depositor can take out its share. + unsafe_set_state(1, PoolState::Destroying).unwrap(); + assert_ok!(fully_unbond_permissioned(10)); - // Then - assert_eq!(Balances::free_balance(&10), 10 + 10); - assert_eq!(Balances::free_balance(&default_bonded_account()), 0); - assert!(!PoolMembers::::contains_key(10)); - // Pools are removed from storage because the depositor left - assert!(!SubPoolsStorage::::contains_key(1),); - assert!(!RewardPools::::contains_key(1),); - assert!(!BondedPools::::contains_key(1),); + current_era += 3; + CurrentEra::set(current_era); + // when + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); assert_eq!( pool_events_since_last_call(), vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, - Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, - Event::Unbonded { member: 550, pool_id: 1, amount: 550 }, - Event::Unbonded { member: 40, pool_id: 1, amount: 40 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 10 }, - Event::Withdrawn { member: 550, pool_id: 1, amount: 545 }, - Event::MemberRemoved { pool_id: 1, member: 550 }, - Event::Withdrawn { member: 40, pool_id: 1, amount: 40 }, - Event::MemberRemoved { pool_id: 1, member: 40 }, - Event::Withdrawn { member: 10, pool_id: 1, amount: 10 }, + Event::Unbonded { member: 10, pool_id: 1, balance: 5, points: 5 }, + Event::Withdrawn { member: 10, pool_id: 1, balance: 5, points: 5 }, Event::MemberRemoved { pool_id: 1, member: 10 }, Event::Destroyed { pool_id: 1 } ] ); + assert_eq!( + balances_events_since_last_call(), + vec![ + BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 }, + BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 } + ] + ); }); } - // This test also documents the case when the pools free balance goes below ED before all - // members have unbonded. #[test] fn withdraw_unbonded_works_against_slashed_with_era_sub_pools() { ExtBuilder::default() .add_members(vec![(40, 40), (550, 550)]) .build_and_execute(|| { + let _ = balances_events_since_last_call(); + // Given - StakingMock::set_bonded_balance(default_bonded_account(), 100); // slash bonded balance - Balances::make_free_balance_be(&default_bonded_account(), 100); - assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(100)); + // current bond is 600, we slash it all to 300. + StakingMock::set_bonded_balance(default_bonded_account(), 300); + Balances::make_free_balance_be(&default_bonded_account(), 300); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(300)); - 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::fully_unbond(Origin::signed(10), 10)); + assert_ok!(fully_unbond_permissioned(40)); + assert_ok!(fully_unbond_permissioned(550)); - SubPoolsStorage::::insert( - 1, - SubPools { - no_era: Default::default(), - with_era: unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 600, balance: 100 }}, - }, + assert_eq!( + SubPoolsStorage::::get(&1).unwrap().with_era, + unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2 + 40 / 2, balance: 550 / 2 + 40 / 2 }} + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, + Event::Unbonded { member: 40, pool_id: 1, balance: 20, points: 20 }, + Event::Unbonded { member: 550, pool_id: 1, balance: 275, points: 275 }, + ] + ); + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::BalanceSet { + who: default_bonded_account(), + free: 300, + reserved: 0 + },] ); + CurrentEra::set(StakingMock::bonding_duration()); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 40, 0)); // Then + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 20 }, + Event::MemberRemoved { pool_id: 1, member: 40 } + ] + ); + assert_eq!( SubPoolsStorage::::get(&1).unwrap().with_era, - unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 560, balance: 94 }} + unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2, balance: 550 / 2 }} ); - assert_eq!(Balances::free_balance(&40), 40 + 6); - assert_eq!(Balances::free_balance(&default_bonded_account()), 94); - assert!(!PoolMembers::::contains_key(40)); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 550, 0)); // Then + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 550, pool_id: 1, balance: 275, points: 275 }, + Event::MemberRemoved { pool_id: 1, member: 550 } + ] + ); + assert!(SubPoolsStorage::::get(&1).unwrap().with_era.is_empty()); + + // now, finally, the depositor can take out its share. + unsafe_set_state(1, PoolState::Destroying).unwrap(); + assert_ok!(fully_unbond_permissioned(10)); + + // because everyone else has left, the points assert_eq!( SubPoolsStorage::::get(&1).unwrap().with_era, - unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 2 }} + unbonding_pools_with_era! { 6 => UnbondPool { points: 5, balance: 5 }} ); - 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!(!PoolMembers::::contains_key(550)); - // When + CurrentEra::set(CurrentEra::get() + 3); + + // when assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); - // Then - assert_eq!(Balances::free_balance(&10), 10 + 0); + // then + assert_eq!(Balances::free_balance(&10), 10 + 5); assert_eq!(Balances::free_balance(&default_bonded_account()), 0); - assert!(!PoolMembers::::contains_key(10)); - // Pools are removed from storage because the depositor left - assert!(!SubPoolsStorage::::contains_key(1)); - assert!(!RewardPools::::contains_key(1)); - assert!(!BondedPools::::contains_key(1)); + // in this test 10 also gets a fair share of the slash, because the slash was + // applied to the bonded account. assert_eq!( pool_events_since_last_call(), vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, - Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, - Event::Unbonded { member: 40, pool_id: 1, amount: 6 }, - Event::Unbonded { member: 550, pool_id: 1, amount: 92 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 2 }, - Event::Withdrawn { member: 40, pool_id: 1, amount: 6 }, - Event::MemberRemoved { pool_id: 1, member: 40 }, - Event::Withdrawn { member: 550, pool_id: 1, amount: 92 }, - Event::MemberRemoved { pool_id: 1, member: 550 }, - Event::Withdrawn { member: 10, pool_id: 1, amount: 0 }, + Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5 }, + Event::Withdrawn { member: 10, pool_id: 1, points: 5, balance: 5 }, Event::MemberRemoved { pool_id: 1, member: 10 }, Event::Destroyed { pool_id: 1 } ] ); + assert_eq!( + balances_events_since_last_call(), + vec![ + BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 }, + BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 } + ] + ); }); } @@ -2562,8 +2662,8 @@ mod withdraw_unbonded { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, - Event::Unbonded { member: 200, pool_id: 1, amount: 200 } + Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100 }, + Event::Unbonded { member: 200, pool_id: 1, points: 200, balance: 200 } ] ); @@ -2590,9 +2690,9 @@ mod withdraw_unbonded { assert_eq!( pool_events_since_last_call(), vec![ - Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, + Event::Withdrawn { member: 100, pool_id: 1, points: 100, balance: 100 }, Event::MemberRemoved { pool_id: 1, member: 100 }, - Event::Withdrawn { member: 200, pool_id: 1, amount: 200 }, + Event::Withdrawn { member: 200, pool_id: 1, points: 200, balance: 200 }, Event::MemberRemoved { pool_id: 1, member: 200 } ] ); @@ -2640,209 +2740,14 @@ mod withdraw_unbonded { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, - Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, + Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100 }, + Event::Withdrawn { member: 100, pool_id: 1, points: 100, balance: 100 }, Event::MemberRemoved { pool_id: 1, member: 100 } ] ); }); } - #[test] - fn withdraw_unbonded_depositor_with_era_pool() { - ExtBuilder::default() - .add_members(vec![(100, 100), (200, 200)]) - .build_and_execute(|| { - // Given - assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); - - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 100 } - ] - ); - - let mut current_era = 1; - CurrentEra::set(current_era); - - assert_ok!(Pools::fully_unbond(Origin::signed(200), 200)); - - assert_eq!( - pool_events_since_last_call(), - vec![Event::Unbonded { member: 200, pool_id: 1, amount: 200 }] - ); - - unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); - - assert_eq!( - pool_events_since_last_call(), - vec![Event::Unbonded { member: 10, pool_id: 1, amount: 10 }] - ); - - assert_eq!( - SubPoolsStorage::::get(1).unwrap(), - SubPools { - no_era: Default::default(), - with_era: unbonding_pools_with_era! { - 0 + 3 => UnbondPool { points: 100, balance: 100}, - 1 + 3 => UnbondPool { points: 200 + 10, balance: 200 + 10 } - } - } - ); - - // 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 member in another `with_era` pool. - assert_noop!( - Pools::withdraw_unbonded(Origin::signed(420), 10, 0), - Error::::NotOnlyPoolMember - ); - - // Given - assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0)); - - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, - Event::MemberRemoved { pool_id: 1, member: 100 } - ] - ); - - assert_eq!( - SubPoolsStorage::::get(1).unwrap(), - SubPools { - no_era: Default::default(), - 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 } - } - } - ); - - // 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::::NotOnlyPoolMember - ); - - // Given - assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 200, 0)); - - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Withdrawn { member: 200, pool_id: 1, amount: 200 }, - Event::MemberRemoved { pool_id: 1, member: 200 } - ] - ); - - assert_eq!( - SubPoolsStorage::::get(1).unwrap(), - SubPools { - no_era: Default::default(), - with_era: unbonding_pools_with_era! { - 1 + 3 => UnbondPool { points: 10, balance: 10 } - } - } - ); - - // The depositor can withdraw - assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 10, 0)); - - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Withdrawn { member: 10, pool_id: 1, amount: 10 }, - Event::MemberRemoved { pool_id: 1, member: 10 }, - Event::Destroyed { pool_id: 1 } - ] - ); - - 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)); - assert!(!RewardPools::::contains_key(1)); - assert!(!BondedPools::::contains_key(1)); - }); - } - - #[test] - fn withdraw_unbonded_depositor_no_era_pool() { - 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(); - 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(); - CurrentEra::set(current_era); - - // Simulate some other withdraw that caused the pool to merge - let sub_pools = - SubPoolsStorage::::get(1).unwrap().maybe_merge_pools(current_era + 3); - SubPoolsStorage::::insert(1, sub_pools); - assert_eq!( - SubPoolsStorage::::get(1).unwrap(), - SubPools { - no_era: UnbondPool { points: 100 + 10, balance: 100 + 10 }, - with_era: Default::default(), - } - ); - - // Cannot withdraw depositor with another member in the `no_era` pool - assert_noop!( - Pools::withdraw_unbonded(Origin::signed(420), 10, 0), - Error::::NotOnlyPoolMember - ); - - // Given - assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0)); - assert_eq!( - SubPoolsStorage::::get(1).unwrap(), - SubPools { - no_era: UnbondPool { points: 10, balance: 10 }, - with_era: Default::default(), - } - ); - - // The depositor can withdraw - assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 10, 0)); - 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)); - assert!(!RewardPools::::contains_key(1)); - assert!(!BondedPools::::contains_key(1)); - - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 10 }, - Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, - Event::MemberRemoved { pool_id: 1, member: 100 }, - Event::Withdrawn { member: 10, pool_id: 1, amount: 10 }, - Event::MemberRemoved { pool_id: 1, member: 10 }, - Event::Destroyed { pool_id: 1 } - ] - ); - }); - } - #[test] fn partial_withdraw_unbonded_depositor() { ExtBuilder::default().ed(1).build_and_execute(|| { @@ -2874,8 +2779,8 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, amount: 6 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 1 } + Event::Unbonded { member: 10, pool_id: 1, points: 6, balance: 6 }, + Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 } ] ); @@ -2906,7 +2811,7 @@ mod withdraw_unbonded { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Withdrawn { member: 10, pool_id: 1, amount: 6 }] + vec![Event::Withdrawn { member: 10, pool_id: 1, points: 6, balance: 6 }] ); // when @@ -2921,7 +2826,7 @@ mod withdraw_unbonded { assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); assert_eq!( pool_events_since_last_call(), - vec![Event::Withdrawn { member: 10, pool_id: 1, amount: 1 },] + vec![Event::Withdrawn { member: 10, pool_id: 1, points: 1, balance: 1 },] ); // when repeating: @@ -2961,8 +2866,8 @@ mod withdraw_unbonded { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 11, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 11, pool_id: 1, amount: 6 }, - Event::Unbonded { member: 11, pool_id: 1, amount: 1 } + Event::Unbonded { member: 11, pool_id: 1, points: 6, balance: 6 }, + Event::Unbonded { member: 11, pool_id: 1, points: 1, balance: 1 } ] ); @@ -2993,7 +2898,7 @@ mod withdraw_unbonded { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Withdrawn { member: 11, pool_id: 1, amount: 6 }] + vec![Event::Withdrawn { member: 11, pool_id: 1, points: 6, balance: 6 }] ); // when @@ -3008,7 +2913,7 @@ mod withdraw_unbonded { assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); assert_eq!( pool_events_since_last_call(), - vec![Event::Withdrawn { member: 11, pool_id: 1, amount: 1 }] + vec![Event::Withdrawn { member: 11, pool_id: 1, points: 1, balance: 1 }] ); // when repeating: @@ -3051,9 +2956,9 @@ mod withdraw_unbonded { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 75 }, - Event::Unbonded { member: 100, pool_id: 1, amount: 25 }, - Event::Withdrawn { member: 100, pool_id: 1, amount: 75 }, + Event::Unbonded { member: 100, pool_id: 1, points: 75, balance: 75 }, + Event::Unbonded { member: 100, pool_id: 1, points: 25, balance: 25 }, + Event::Withdrawn { member: 100, pool_id: 1, points: 75, balance: 75 }, ] ); assert_eq!( @@ -3067,7 +2972,7 @@ mod withdraw_unbonded { assert_eq!( pool_events_since_last_call(), vec![ - Event::Withdrawn { member: 100, pool_id: 1, amount: 25 }, + Event::Withdrawn { member: 100, pool_id: 1, points: 25, balance: 25 }, Event::MemberRemoved { pool_id: 1, member: 100 } ] ); @@ -3102,9 +3007,9 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, amount: 7 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 3 }, - Event::Withdrawn { member: 10, pool_id: 1, amount: 7 } + Event::Unbonded { member: 10, pool_id: 1, points: 7, balance: 7 }, + Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 }, + Event::Withdrawn { member: 10, pool_id: 1, points: 7, balance: 7 } ] ); assert_eq!( @@ -3118,7 +3023,7 @@ mod withdraw_unbonded { assert_eq!( pool_events_since_last_call(), vec![ - Event::Withdrawn { member: 10, pool_id: 1, amount: 3 }, + Event::Withdrawn { member: 10, pool_id: 1, points: 3, balance: 3 }, Event::MemberRemoved { pool_id: 1, member: 10 }, // the pool is also destroyed now. Event::Destroyed { pool_id: 1 }, @@ -3288,11 +3193,11 @@ mod nominate { // Root can nominate assert_ok!(Pools::nominate(Origin::signed(900), 1, vec![21])); - assert_eq!(Nominations::get(), vec![21]); + assert_eq!(Nominations::get().unwrap(), vec![21]); // Nominator can nominate assert_ok!(Pools::nominate(Origin::signed(901), 1, vec![31])); - assert_eq!(Nominations::get(), vec![31]); + assert_eq!(Nominations::get().unwrap(), vec![31]); // Can't nominate for a pool that doesn't exist assert_noop!( diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index ca89f982c60e3..8e3facfc5ec26 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -18,11 +18,12 @@ //! Autogenerated weights for pallet_nomination_pools //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-06-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// target/production/substrate // benchmark // pallet // --chain=dev @@ -32,8 +33,9 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/nomination-pools/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -58,6 +60,7 @@ pub trait WeightInfo { fn set_metadata(n: u32, ) -> Weight; fn set_configs() -> Weight; fn update_roles() -> Weight; + fn chill() -> Weight; } /// Weights for pallet_nomination_pools using the Substrate node and recommended hardware. @@ -77,7 +80,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (129_124_000 as Weight) + (124_508_000 as Weight) .saturating_add(T::DbWeight::get().reads(17 as Weight)) .saturating_add(T::DbWeight::get().writes(11 as Weight)) } @@ -91,7 +94,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (118_193_000 as Weight) + (115_185_000 as Weight) .saturating_add(T::DbWeight::get().reads(13 as Weight)) .saturating_add(T::DbWeight::get().writes(12 as Weight)) } @@ -102,19 +105,19 @@ impl WeightInfo for SubstrateWeight { // 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 ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (132_390_000 as Weight) - .saturating_add(T::DbWeight::get().reads(13 as Weight)) - .saturating_add(T::DbWeight::get().writes(12 as Weight)) + (132_723_000 as Weight) + .saturating_add(T::DbWeight::get().reads(14 as Weight)) + .saturating_add(T::DbWeight::get().writes(13 as Weight)) } // 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 { - (54_743_000 as Weight) + (52_498_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -133,7 +136,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (124_684_000 as Weight) + (121_645_000 as Weight) .saturating_add(T::DbWeight::get().reads(18 as Weight)) .saturating_add(T::DbWeight::get().writes(13 as Weight)) } @@ -141,10 +144,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) + /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (44_259_000 as Weight) + (43_320_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((49_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -156,10 +160,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - (84_854_000 as Weight) - // Standard Error: 0 - .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) + (83_195_000 as Weight) + // Standard Error: 5_000 + .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(7 as Weight)) } @@ -182,8 +187,9 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) + /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (146_992_000 as Weight) + (143_495_000 as Weight) .saturating_add(T::DbWeight::get().reads(19 as Weight)) .saturating_add(T::DbWeight::get().writes(16 as Weight)) } @@ -210,7 +216,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (138_099_000 as Weight) + (127_998_000 as Weight) .saturating_add(T::DbWeight::get().reads(22 as Weight)) .saturating_add(T::DbWeight::get().writes(15 as Weight)) } @@ -226,10 +232,11 @@ 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) + /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - (50_964_000 as Weight) - // Standard Error: 11_000 - .saturating_add((2_333_000 as Weight).saturating_mul(n as Weight)) + (49_929_000 as Weight) + // Standard Error: 16_000 + .saturating_add((2_319_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)) @@ -237,15 +244,16 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (27_196_000 as Weight) + (27_399_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:0) // Storage: NominationPools Metadata (r:1 w:1) // Storage: NominationPools CounterForMetadata (r:1 w:1) + /// The range of component `n` is `[1, 256]`. fn set_metadata(n: u32, ) -> Weight { - (15_056_000 as Weight) + (14_813_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)) @@ -257,15 +265,28 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (6_294_000 as Weight) + (6_115_000 as Weight) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - (22_444_000 as Weight) + (22_546_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: BagsList ListNodes (r:1 w:1) + // Storage: BagsList ListBags (r:1 w:1) + // Storage: BagsList CounterForListNodes (r:1 w:1) + fn chill() -> Weight { + (48_243_000 as Weight) + .saturating_add(T::DbWeight::get().reads(8 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } } // For backwards compatibility and tests @@ -284,7 +305,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (129_124_000 as Weight) + (124_508_000 as Weight) .saturating_add(RocksDbWeight::get().reads(17 as Weight)) .saturating_add(RocksDbWeight::get().writes(11 as Weight)) } @@ -298,7 +319,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (118_193_000 as Weight) + (115_185_000 as Weight) .saturating_add(RocksDbWeight::get().reads(13 as Weight)) .saturating_add(RocksDbWeight::get().writes(12 as Weight)) } @@ -309,19 +330,19 @@ impl WeightInfo for () { // 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 ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (132_390_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(13 as Weight)) - .saturating_add(RocksDbWeight::get().writes(12 as Weight)) + (132_723_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(14 as Weight)) + .saturating_add(RocksDbWeight::get().writes(13 as Weight)) } // 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 { - (54_743_000 as Weight) + (52_498_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -340,7 +361,7 @@ impl WeightInfo for () { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (124_684_000 as Weight) + (121_645_000 as Weight) .saturating_add(RocksDbWeight::get().reads(18 as Weight)) .saturating_add(RocksDbWeight::get().writes(13 as Weight)) } @@ -348,10 +369,11 @@ impl WeightInfo for () { // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) + /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (44_259_000 as Weight) + (43_320_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((49_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -363,10 +385,11 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - (84_854_000 as Weight) - // Standard Error: 0 - .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) + (83_195_000 as Weight) + // Standard Error: 5_000 + .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } @@ -389,8 +412,9 @@ impl WeightInfo for () { // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) + /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (146_992_000 as Weight) + (143_495_000 as Weight) .saturating_add(RocksDbWeight::get().reads(19 as Weight)) .saturating_add(RocksDbWeight::get().writes(16 as Weight)) } @@ -417,7 +441,7 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (138_099_000 as Weight) + (127_998_000 as Weight) .saturating_add(RocksDbWeight::get().reads(22 as Weight)) .saturating_add(RocksDbWeight::get().writes(15 as Weight)) } @@ -433,10 +457,11 @@ impl WeightInfo for () { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) + /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - (50_964_000 as Weight) - // Standard Error: 11_000 - .saturating_add((2_333_000 as Weight).saturating_mul(n as Weight)) + (49_929_000 as Weight) + // Standard Error: 16_000 + .saturating_add((2_319_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)) @@ -444,15 +469,16 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (27_196_000 as Weight) + (27_399_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:0) // Storage: NominationPools Metadata (r:1 w:1) // Storage: NominationPools CounterForMetadata (r:1 w:1) + /// The range of component `n` is `[1, 256]`. fn set_metadata(n: u32, ) -> Weight { - (15_056_000 as Weight) + (14_813_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)) @@ -464,13 +490,26 @@ impl WeightInfo for () { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (6_294_000 as Weight) + (6_115_000 as Weight) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - (22_444_000 as Weight) + (22_546_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: BagsList ListNodes (r:1 w:1) + // Storage: BagsList ListBags (r:1 w:1) + // Storage: BagsList CounterForListNodes (r:1 w:1) + fn chill() -> Weight { + (48_243_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(8 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } } diff --git a/frame/nomination-pools/test-staking/Cargo.toml b/frame/nomination-pools/test-staking/Cargo.toml new file mode 100644 index 0000000000000..ad36e89e0d68a --- /dev/null +++ b/frame/nomination-pools/test-staking/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "pallet-nomination-pools-test-staking" +version = "1.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 tests with the staking pallet" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dev-dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +scale-info = { version = "2.0.1", features = ["derive"] } + +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-io = { version = "6.0.0", path = "../../../primitives/io" } +sp-std = { version = "4.0.0", path = "../../../primitives/std" } +sp-staking = { version = "4.0.0-dev", path = "../../../primitives/staking" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } + +frame-system = { version = "4.0.0-dev", path = "../../system" } +frame-support = { version = "4.0.0-dev", path = "../../support" } +frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support" } + +pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } +pallet-balances = { version = "4.0.0-dev", path = "../../balances" } +pallet-staking = { version = "4.0.0-dev", path = "../../staking" } +pallet-bags-list = { version = "4.0.0-dev", path = "../../bags-list" } +pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } +pallet-nomination-pools = { version = "1.0.0-dev", path = ".." } + +sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } +log = { version = "0.4.0" } diff --git a/frame/nomination-pools/test-staking/src/lib.rs b/frame/nomination-pools/test-staking/src/lib.rs new file mode 100644 index 0000000000000..2e40e8c6d917d --- /dev/null +++ b/frame/nomination-pools/test-staking/src/lib.rs @@ -0,0 +1,373 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(test)] + +mod mock; + +use frame_support::{assert_noop, assert_ok, bounded_btree_map, traits::Currency}; +use mock::*; +use pallet_nomination_pools::{ + Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember, PoolMembers, PoolState, +}; +use pallet_staking::{CurrentEra, Event as StakingEvent, Payee, RewardDestination}; + +#[test] +fn pool_lifecycle_e2e() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::minimum_balance(), 5); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(Origin::signed(10), 50, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + // have the pool nominate. + assert_ok!(Pools::nominate(Origin::signed(10), 1, vec![1, 2, 3])); + + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Bonded(POOL1_BONDED, 50),]); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, + ] + ); + + // have two members join + assert_ok!(Pools::join(Origin::signed(20), 10, 1)); + assert_ok!(Pools::join(Origin::signed(21), 10, 1)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded(POOL1_BONDED, 10), StakingEvent::Bonded(POOL1_BONDED, 10),] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true }, + ] + ); + + // pool goes into destroying + assert_ok!(Pools::set_state(Origin::signed(10), 1, PoolState::Destroying)); + + // depositor cannot unbond yet. + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 50), + PoolsError::::NotOnlyPoolMember, + ); + + // now the members want to unbond. + assert_ok!(Pools::unbond(Origin::signed(20), 20, 10)); + assert_ok!(Pools::unbond(Origin::signed(21), 21, 10)); + + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_eras.len(), 1); + assert_eq!(PoolMembers::::get(20).unwrap().points, 0); + assert_eq!(PoolMembers::::get(21).unwrap().unbonding_eras.len(), 1); + assert_eq!(PoolMembers::::get(21).unwrap().points, 0); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded(POOL1_BONDED, 10), + StakingEvent::Unbonded(POOL1_BONDED, 10), + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, points: 10, balance: 10 }, + PoolsEvent::Unbonded { member: 21, pool_id: 1, points: 10, balance: 10 }, + ] + ); + + // depositor cannot still unbond + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 50), + PoolsError::::NotOnlyPoolMember, + ); + + for e in 1..BondingDuration::get() { + CurrentEra::::set(Some(e)); + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(20), 20, 0), + PoolsError::::CannotWithdrawAny + ); + } + + // members are now unlocked. + CurrentEra::::set(Some(BondingDuration::get())); + + // depositor cannot still unbond + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 50), + PoolsError::::NotOnlyPoolMember, + ); + + // but members can now withdraw. + assert_ok!(Pools::withdraw_unbonded(Origin::signed(20), 20, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(21), 21, 0)); + assert!(PoolMembers::::get(20).is_none()); + assert!(PoolMembers::::get(21).is_none()); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn(POOL1_BONDED, 20),] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 20, pool_id: 1, points: 10, balance: 10 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 20 }, + PoolsEvent::Withdrawn { member: 21, pool_id: 1, points: 10, balance: 10 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 21 }, + ] + ); + + // as soon as all members have left, the depositor can try to unbond, but since the + // min-nominator intention is set, they must chill first. + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 50), + pallet_staking::Error::::InsufficientBond + ); + + assert_ok!(Pools::chill(Origin::signed(10), 1)); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 50)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Chilled(POOL1_BONDED), StakingEvent::Unbonded(POOL1_BONDED, 50),] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50 }] + ); + + // waiting another bonding duration: + CurrentEra::::set(Some(BondingDuration::get() * 2)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 1)); + + // pools is fully destroyed now. + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn(POOL1_BONDED, 50),] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 10 }, + PoolsEvent::Destroyed { pool_id: 1 } + ] + ); + }) +} + +#[test] +fn pool_slash_e2e() { + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(Origin::signed(10), 40, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Bonded(POOL1_BONDED, 40)]); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, + ] + ); + + assert_eq!(Payee::::get(POOL1_BONDED), RewardDestination::Account(POOL1_REWARD)); + + // have two members join + assert_ok!(Pools::join(Origin::signed(20), 20, 1)); + assert_ok!(Pools::join(Origin::signed(21), 20, 1)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded(POOL1_BONDED, 20), StakingEvent::Bonded(POOL1_BONDED, 20)] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 20, joined: true }, + ] + ); + + // now let's progress a bit. + CurrentEra::::set(Some(1)); + + // 20 / 80 of the total funds are unlocked, and safe from any further slash. + assert_ok!(Pools::unbond(Origin::signed(10), 10, 10)); + assert_ok!(Pools::unbond(Origin::signed(20), 20, 10)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded(POOL1_BONDED, 10), + StakingEvent::Unbonded(POOL1_BONDED, 10) + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10 }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10 } + ] + ); + + CurrentEra::::set(Some(2)); + + // note: depositor cannot fully unbond at this point. + // these funds will still get slashed. + assert_ok!(Pools::unbond(Origin::signed(10), 10, 10)); + assert_ok!(Pools::unbond(Origin::signed(20), 20, 10)); + assert_ok!(Pools::unbond(Origin::signed(21), 21, 10)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded(POOL1_BONDED, 10), + StakingEvent::Unbonded(POOL1_BONDED, 10), + StakingEvent::Unbonded(POOL1_BONDED, 10), + ] + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10 }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10 }, + PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10 }, + ] + ); + + // At this point, 20 are safe from slash, 30 are unlocking but vulnerable to slash, and and + // another 30 are active and vulnerable to slash. Let's slash half of them. + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 30, + &mut Default::default(), + &mut Default::default(), + 1, // slash era 1, affects chunks at era 5 onwards. + ); + + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Slashed(POOL1_BONDED, 30)]); + assert_eq!( + pool_events_since_last_call(), + vec![ + // 30 has been slashed to 15 (15 slash) + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 5, balance: 15 }, + // 30 has been slashed to 15 (15 slash) + PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 } + ] + ); + + CurrentEra::::set(Some(3)); + assert_ok!(Pools::unbond(Origin::signed(21), 21, 10)); + + assert_eq!( + PoolMembers::::get(21).unwrap(), + PoolMember { + pool_id: 1, + points: 0, + reward_pool_total_earnings: 0, + // the 10 points unlocked just now correspond to 5 points in the unbond pool. + unbonding_eras: bounded_btree_map!(5 => 10, 6 => 5) + } + ); + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Unbonded(POOL1_BONDED, 5)]); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 5, points: 5 }] + ); + + // now we start withdrawing. we do it all at once, at era 6 where 20 and 21 are fully free. + CurrentEra::::set(Some(6)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(20), 20, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(21), 21, 0)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + // 20 had unbonded 10 safely, and 10 got slashed by half. + PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 10 + 5, points: 20 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 20 }, + // 21 unbonded all of it after the slash + PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 5 + 5, points: 15 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 21 } + ] + ); + assert_eq!( + staking_events_since_last_call(), + // a 10 (un-slashed) + 10/2 (slashed) balance from 10 has also been unlocked + vec![StakingEvent::Withdrawn(POOL1_BONDED, 15 + 10 + 15)] + ); + + // now, finally, we can unbond the depositor further than their current limit. + assert_ok!(Pools::set_state(Origin::signed(10), 1, PoolState::Destroying)); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 20)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded(POOL1_BONDED, 10)] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10 } + ] + ); + + CurrentEra::::set(Some(9)); + assert_eq!( + PoolMembers::::get(10).unwrap(), + PoolMember { + pool_id: 1, + points: 0, + reward_pool_total_earnings: 0, + unbonding_eras: bounded_btree_map!(4 => 10, 5 => 10, 9 => 10) + } + ); + // withdraw the depositor, they should lose 12 balance in total due to slash. + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn(POOL1_BONDED, 10)] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 10, pool_id: 1, balance: 10 + 15, points: 30 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 10 }, + PoolsEvent::Destroyed { pool_id: 1 } + ] + ); + }); +} diff --git a/frame/nomination-pools/test-staking/src/mock.rs b/frame/nomination-pools/test-staking/src/mock.rs new file mode 100644 index 0000000000000..7b720c009b29b --- /dev/null +++ b/frame/nomination-pools/test-staking/src/mock.rs @@ -0,0 +1,261 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use frame_election_provider_support::VoteWeight; +use frame_support::{assert_ok, pallet_prelude::*, parameter_types, traits::ConstU64, PalletId}; +use sp_runtime::traits::{Convert, IdentityLookup}; + +type AccountId = u128; +type AccountIndex = u32; +type BlockNumber = u64; +type Balance = u128; + +pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128; +pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128; + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Index = AccountIndex; + type BlockNumber = BlockNumber; + type Call = Call; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = sp_runtime::testing::Header; + type Event = Event; + type BlockHashCount = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<5>; + type WeightInfo = (); +} + +parameter_types! { + pub static ExistentialDeposit: Balance = 5; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +pallet_staking_reward_curve::build! { + const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); +} + +parameter_types! { + pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; + pub static BondingDuration: u32 = 3; +} + +impl pallet_staking::Config for Runtime { + type MaxNominations = ConstU32<16>; + type Currency = Balances; + type CurrencyBalance = Balance; + type UnixTime = pallet_timestamp::Pallet; + type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type RewardRemainder = (); + type Event = Event; + type Slash = (); + type Reward = (); + type SessionsPerEra = (); + type SlashDeferDuration = (); + type SlashCancelOrigin = frame_system::EnsureRoot; + type BondingDuration = BondingDuration; + type SessionInterface = (); + type EraPayout = pallet_staking::ConvertCurve; + type NextNewSession = (); + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = (); + type ElectionProvider = + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = pallet_bags_list::Pallet; + type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = Pools; + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type WeightInfo = (); +} + +parameter_types! { + pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; +} + +impl pallet_bags_list::Config for Runtime { + type Event = Event; + type WeightInfo = (); + type BagThresholds = BagThresholds; + type ScoreProvider = Staking; + type Score = VoteWeight; +} + +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> sp_core::U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub const PostUnbondingPoolsWindow: u32 = 10; + pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); +} + +impl pallet_nomination_pools::Config for Runtime { + type Event = Event; + type WeightInfo = (); + type Currency = Balances; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type StakingInterface = Staking; + type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; + type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = ConstU32<8>; + type MinPointsToBalance = ConstU32<10>; + type PalletId = PoolsPalletId; +} + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + Call: From, +{ + type OverarchingCall = Call; + type Extrinsic = UncheckedExtrinsic; +} + +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, + BagsList: pallet_bags_list::{Pallet, Call, Storage, Event}, + Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let _ = pallet_nomination_pools::GenesisConfig:: { + min_join_bond: 2, + min_create_bond: 2, + max_pools: Some(3), + max_members_per_pool: Some(3), + max_members: Some(3 * 3), + } + .assimilate_storage(&mut storage) + .unwrap(); + + let _ = pallet_balances::GenesisConfig:: { + balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)], + } + .assimilate_storage(&mut storage) + .unwrap(); + + let mut ext = sp_io::TestExternalities::from(storage); + + ext.execute_with(|| { + // for events to be deposited. + frame_system::Pallet::::set_block_number(1); + + // set some limit for nominations. + assert_ok!(Staking::set_staking_configs( + Origin::root(), + pallet_staking::ConfigOp::Set(10), // minimum nominator bond + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + )); + }); + + ext +} + +parameter_types! { + static ObservedEventsPools: usize = 0; + static ObservedEventsStaking: usize = 0; + static ObservedEventsBalances: usize = 0; +} + +pub(crate) fn pool_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::Pools(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = ObservedEventsPools::get(); + ObservedEventsPools::set(events.len()); + events.into_iter().skip(already_seen).collect() +} + +pub(crate) fn staking_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::Staking(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = ObservedEventsStaking::get(); + ObservedEventsStaking::set(events.len()); + events.into_iter().skip(already_seen).collect() +} diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 331095774b741..360d5b5efb58f 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -438,7 +438,7 @@ pub struct UnlockChunk { } /// The ledger of a (bonded) stash. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebugNoBound, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct StakingLedger { /// The stash account whose balance is actually locked and at stake. @@ -607,12 +607,12 @@ impl StakingLedger { let mut slashed_unlocking = BTreeMap::<_, _>::new(); 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); - // write the new slashed value of this chunk to the map. - slashed_unlocking.insert(chunk.era, chunk.value); if remaining_slash.is_zero() { break } + slash_out_of(&mut chunk.value, &mut remaining_slash); + // write the new slashed value of this chunk to the map. + slashed_unlocking.insert(chunk.era, chunk.value); } else { break } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 66265ab1f135e..7656eec80a5ff 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1414,17 +1414,17 @@ impl StakingInterface for Pallet { Self::unbond(RawOrigin::Signed(controller).into(), value) } + fn chill(controller: Self::AccountId) -> DispatchResult { + Self::chill(RawOrigin::Signed(controller).into()) + } + fn withdraw_unbonded( controller: Self::AccountId, num_slashing_spans: u32, - ) -> Result { - 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) + ) -> Result { + Self::withdraw_unbonded(RawOrigin::Signed(controller.clone()).into(), num_slashing_spans) + .map(|_| !Ledger::::contains_key(&controller)) + .map_err(|with_post| with_post.error) } fn bond( @@ -1445,4 +1445,9 @@ impl StakingInterface for Pallet { let targets = targets.into_iter().map(T::Lookup::unlookup).collect::>(); Self::nominate(RawOrigin::Signed(controller).into(), targets) } + + #[cfg(feature = "runtime-benchmarks")] + fn nominations(who: Self::AccountId) -> Option> { + Nominators::::get(who).map(|n| n.targets.into_inner()) + } } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index d0ebef27b4ef6..9a13a818f4b59 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4930,6 +4930,25 @@ fn force_apply_min_commission_works() { }); } +#[test] +fn proportional_slash_stop_slashing_if_remaining_zero() { + let c = |era, value| UnlockChunk:: { era, value }; + // Given + let mut ledger = StakingLedger:: { + stash: 123, + total: 40, + active: 20, + // we have some chunks, but they are not affected. + unlocking: bounded_vec![c(1, 10), c(2, 10)], + claimed_rewards: vec![], + }; + + assert_eq!(BondingDuration::get(), 3); + + // should not slash more than the amount requested, by accidentally slashing the first chunk. + assert_eq!(ledger.slash(18, 1, 0), 18); +} + #[test] fn proportional_ledger_slash_works() { let c = |era, value| UnlockChunk:: { era, value }; diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 6911da630cb34..d9e50a1e1345e 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -1267,7 +1267,7 @@ pub trait StoragePrefixedMap { pub trait StorageAppend: private::Sealed {} /// Marker trait that will be implemented for types that support to decode their length in an -/// effificent way. It is expected that the length is at the beginning of the encoded object +/// efficient way. It is expected that the length is at the beginning of the encoded object /// and that the length is a `Compact`. /// /// This trait is sealed. diff --git a/frame/support/src/storage/transactional.rs b/frame/support/src/storage/transactional.rs index d1bdb30af947b..909d5909ed8bd 100644 --- a/frame/support/src/storage/transactional.rs +++ b/frame/support/src/storage/transactional.rs @@ -164,9 +164,9 @@ where /// Execute the supplied function, adding a new storage layer. /// -/// This is the same as `with_transaction`, but assuming that any function returning -/// an `Err` should rollback, and any function returning `Ok` should commit. This -/// provides a cleaner API to the developer who wants this behavior. +/// This is the same as `with_transaction`, but assuming that any function returning an `Err` should +/// rollback, and any function returning `Ok` should commit. This provides a cleaner API to the +/// developer who wants this behavior. pub fn with_storage_layer(f: F) -> Result where E: From, diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index a2a6432485be8..5a3e97b4d5274 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -108,6 +108,9 @@ pub trait StakingInterface { validators: sp_std::vec::Vec, ) -> DispatchResult; + /// Chill `stash`. + fn chill(controller: Self::AccountId) -> 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. @@ -125,8 +128,14 @@ pub trait StakingInterface { fn unbond(stash: Self::AccountId, value: Self::Balance) -> DispatchResult; /// Unlock any funds schedule to unlock before or at the current era. + /// + /// Returns whether the stash was killed because of this withdraw or not. fn withdraw_unbonded( stash: Self::AccountId, num_slashing_spans: u32, - ) -> Result; + ) -> Result; + + /// Get the nominations of a stash, if they are a nominator, `None` otherwise. + #[cfg(feature = "runtime-benchmarks")] + fn nominations(who: Self::AccountId) -> Option>; }