Revamp nomination pool reward scheme (#11669)

* make pool roles optional

* undo lock file changes?

* add migration

* add the ability for pools to chill themselves

* boilerplate of tests

* somewhat stable, but I think I found another bug as well

* Fix it all

* Add more more sophisticated test + capture one more bug.

* Update frame/staking/src/lib.rs

* reduce the diff a little bit

* add some test for the slashing bug

* cleanup

* fix lock file?

* Fix

* fmt

* Update frame/nomination-pools/src/lib.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update frame/nomination-pools/src/lib.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update frame/nomination-pools/src/lib.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update frame/nomination-pools/src/mock.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix build

* fix some fishy tests..

* add one last integrity check for MinCreateBond

* remove bad assertion -- needs to be dealt with later

* nits

* fix tests and add benchmarks for chill

* remove stuff

* fix benchmarks

* cargo run --quiet --profile=production  --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_nomination_pools --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/nomination-pools/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* remove defensive

* first working version

* bring back all tests

* ALL new tests work now

* cleanup

* make sure benchmarks and all work

* cargo run --quiet --profile=production  --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_nomination_pools --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/nomination-pools/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* round of self-review, make arithmetic safe

* fix warn

* add migration code

* Fix doc

* add precision notes

* make arithmetic fallible

* fix node runtime

* a lot of precision tests and notes and stuff

* document MaxPOintsToBalance better

* :round of self-review

* fmt

* fix some comments

* Fix proportional slashing logic

* Update frame/nomination-pools/src/tests.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update frame/nomination-pools/src/tests.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update frame/nomination-pools/src/lib.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* track poinst in migration

* fix

* fmt

* fix migration

* remove event read

* Apply suggestions from code review

* Update frame/staking/src/lib.rs

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* Update frame/nomination-pools/src/lib.rs

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* Update frame/nomination-pools/src/lib.rs

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* update

* fmt

* fmt

* add one last test

* fmt

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: Parity Bot <admin@parity.io>
This commit is contained in:
Kian Paimani
2022-07-13 13:49:20 +01:00
committed by GitHub
parent 5d96c0a0ea
commit f8d4b99917
10 changed files with 1991 additions and 963 deletions
+6 -3
View File
@@ -68,7 +68,7 @@ use sp_runtime::{
SaturatedConversion, StaticLookup,
},
transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity},
ApplyExtrinsicResult, FixedPointNumber, Perbill, Percent, Permill, Perquintill,
ApplyExtrinsicResult, FixedPointNumber, FixedU128, Perbill, Percent, Permill, Perquintill,
};
use sp_std::prelude::*;
#[cfg(any(feature = "std", test))]
@@ -730,7 +730,7 @@ impl pallet_bags_list::Config for Runtime {
parameter_types! {
pub const PostUnbondPoolsWindow: u32 = 4;
pub const NominationPoolsPalletId: PalletId = PalletId(*b"py/nopls");
pub const MinPointsToBalance: u32 = 10;
pub const MaxPointsToBalance: u8 = 10;
}
use sp_runtime::traits::Convert;
@@ -751,6 +751,8 @@ impl pallet_nomination_pools::Config for Runtime {
type WeightInfo = ();
type Event = Event;
type Currency = Balances;
type CurrencyBalance = Balance;
type RewardCounter = FixedU128;
type BalanceToU256 = BalanceToU256;
type U256ToBalance = U256ToBalance;
type StakingInterface = pallet_staking::Pallet<Self>;
@@ -758,7 +760,7 @@ impl pallet_nomination_pools::Config for Runtime {
type MaxMetadataLen = ConstU32<256>;
type MaxUnbonding = ConstU32<8>;
type PalletId = NominationPoolsPalletId;
type MinPointsToBalance = MinPointsToBalance;
type MaxPointsToBalance = MaxPointsToBalance;
}
parameter_types! {
@@ -1675,6 +1677,7 @@ pub type Executive = frame_executive::Executive<
frame_system::ChainContext<Runtime>,
Runtime,
AllPalletsWithSystem,
pallet_nomination_pools::migration::v2::MigrateToV2<Runtime>,
>;
/// MMR helper types.
@@ -17,7 +17,10 @@
use frame_election_provider_support::VoteWeight;
use frame_support::{pallet_prelude::*, parameter_types, traits::ConstU64, PalletId};
use sp_runtime::traits::{Convert, IdentityLookup};
use sp_runtime::{
traits::{Convert, IdentityLookup},
FixedU128,
};
type AccountId = u128;
type AccountIndex = u32;
@@ -144,13 +147,15 @@ impl Convert<sp_core::U256, Balance> for U256ToBalance {
parameter_types! {
pub static PostUnbondingPoolsWindow: u32 = 10;
pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
pub const MinPointsToBalance: u32 = 10;
pub const MaxPointsToBalance: u8 = 10;
}
impl pallet_nomination_pools::Config for Runtime {
type Event = Event;
type WeightInfo = ();
type Currency = Balances;
type CurrencyBalance = Balance;
type RewardCounter = FixedU128;
type BalanceToU256 = BalanceToU256;
type U256ToBalance = U256ToBalance;
type StakingInterface = Staking;
@@ -158,7 +163,7 @@ impl pallet_nomination_pools::Config for Runtime {
type MaxMetadataLen = ConstU32<256>;
type MaxUnbonding = ConstU32<8>;
type PalletId = PoolsPalletId;
type MinPointsToBalance = MinPointsToBalance;
type MaxPointsToBalance = MaxPointsToBalance;
}
impl crate::Config for Runtime {}
+289 -281
View File
@@ -17,8 +17,8 @@
//! # Nomination Pools for Staking Delegation
//!
//! A pallet that allows members to delegate their stake to nominating pools. A nomination pool
//! acts as nominator and nominates validators on the members behalf.
//! A pallet that allows members to delegate their stake to nominating pools. A nomination pool acts
//! as nominator and nominates validators on the members behalf.
//!
//! # Index
//!
@@ -28,16 +28,27 @@
//!
//! ## Key terms
//!
//! * pool id: A unique identifier of each pool. Set to u12
//! * bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and
//! [`BondedPoolInner`]. Bonded pools are identified via the pools bonded account.
//! [`BondedPoolInner`].
//! * reward pool: Tracks rewards earned by actively staked funds. See [`RewardPool`] and
//! [`RewardPools`]. Reward pools are identified via the pools bonded account.
//! [`RewardPools`].
//! * unbonding sub pools: Collection of pools at different phases of the unbonding lifecycle. See
//! [`SubPools`] and [`SubPoolsStorage`]. Sub pools are identified via the pools bonded account.
//! * members: Accounts that are members of pools. See [`PoolMember`] and [`PoolMembers`]. Pool
//! members are identified via their account.
//! * point: A unit of measure for a members portion of a pool's funds.
//! [`SubPools`] and [`SubPoolsStorage`].
//! * members: Accounts that are members of pools. See [`PoolMember`] and [`PoolMembers`].
//! * roles: Administrative roles of each pool, capable of controlling nomination, and the state of
//! the pool.
//! * point: A unit of measure for a members portion of a pool's funds. Points initially have a
//! ratio of 1 (as set by `POINTS_TO_BALANCE_INIT_RATIO`) to balance, but as slashing happens,
//! this can change.
//! * kick: The act of a pool administrator forcibly ejecting a member.
//! * bonded account: A key-less account id derived from the pool id that acts as the bonded
//! account. This account registers itself as a nominator in the staking system, and follows
//! exactly the same rules and conditions as a normal staker. Its bond increases or decreases as
//! members join, it can `nominate` or `chill`, and might not even earn staking rewards if it is
//! not nominating proper validators.
//! * reward account: A similar key-less account, that is set as the `Payee` account fo the bonded
//! account for all staking rewards.
//!
//! ## Usage
//!
@@ -55,11 +66,13 @@
//!
//! In order to leave, a member must take two steps.
//!
//! First, they must call [`Call::unbond`]. The unbond other extrinsic will start the
//! unbonding process by unbonding all of the members funds.
//! First, they must call [`Call::unbond`]. The unbond extrinsic will start the unbonding process by
//! unbonding all or a portion of the members funds.
//!
//! Second, once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the member
//! can call [`Call::withdraw_unbonded`] to withdraw all their funds.
//! > A member can have up to [`Config::MaxUnbonding`] distinct active unbonding requests.
//!
//! Second, once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the member can
//! call [`Call::withdraw_unbonded`] to withdraw any funds that are free.
//!
//! For design docs see the [bonded pool](#bonded-pool) and [unbonding sub
//! pools](#unbonding-sub-pools) sections.
@@ -67,9 +80,13 @@
//! ### Slashes
//!
//! Slashes are distributed evenly across the bonded pool and the unbonding pools from slash era+1
//! through the slash apply era. Thus, any member who either a) unbonded or b) was actively
//! bonded in the aforementioned range of eras will be affected by the slash. A member is slashed
//! pro-rata based on its stake relative to the total slash amount.
//! through the slash apply era. Thus, any member who either
//!
//! 1. unbonded, or
//! 2. was actively bonded
//
//! in the aforementioned range of eras will be affected by the slash. A member is slashed pro-rata
//! based on its stake relative to the total slash amount.
//!
//! For design docs see the [slashing](#slashing) section.
//!
@@ -82,7 +99,9 @@
//! To help facilitate pool administration the pool has one of three states (see [`PoolState`]):
//!
//! * Open: Anyone can join the pool and no members can be permissionlessly removed.
//! * Blocked: No members can join and some admin roles can kick members.
//! * Blocked: No members can join and some admin roles can kick members. Kicking is not instant,
//! and follows the same process of `unbond` and then `withdraw_unbonded`. In other words,
//! administrators can permissionlessly unbond other members.
//! * Destroying: No members can join and all members can be permissionlessly removed with
//! [`Call::unbond`] and [`Call::withdraw_unbonded`]. Once a pool is in destroying state, it
//! cannot be reverted to another state.
@@ -90,12 +109,23 @@
//! A pool has 4 administrative roles (see [`PoolRoles`]):
//!
//! * Depositor: creates the pool and is the initial member. They can only leave the pool once all
//! other members have left. Once they fully leave the pool is destroyed.
//! other members have left. Once they fully withdraw their funds, the pool is destroyed.
//! * Nominator: can select which validators the pool nominates.
//! * State-Toggler: can change the pools state and kick members if the pool is blocked.
//! * Root: can change the nominator, state-toggler, or itself and can perform any of the actions
//! the nominator or state-toggler can.
//!
//! ### Dismantling
//!
//! As noted, a pool is destroyed once
//!
//! 1. First, all members need to fully unbond and withdraw. If the pool state is set to
//! `Destroying`, this can happen permissionlessly.
//! 2. The depositor itself fully unbonds and withdraws. Note that at this point, based on the
//! requirements of the staking system, the pool's bonded account's stake might not be able to ge
//! below a certain threshold as a nominator. At this point, the pool should `chill` itself to
//! allow the depositor to leave.
//!
//! ## Design
//!
//! _Notes_: this section uses pseudo code to explain general design and does not necessarily
@@ -108,8 +138,8 @@
//! members that where in the pool while it was backing a validator that got slashed.
//! * Maximize scalability in terms of member count.
//!
//! In order to maintain scalability, all operations are independent of the number of members. To
//! do this, delegation specific information is stored local to the member while the pool data
//! In order to maintain scalability, all operations are independent of the number of members. To do
//! this, delegation specific information is stored local to the member while the pool data
//! structures have bounded datum.
//!
//! ### Bonded pool
@@ -118,9 +148,9 @@
//! unbonding. The total points of a bonded pool are always equal to the sum of points of the
//! delegation members. A bonded pool tracks its points and reads its bonded balance.
//!
//! When a member joins a pool, `amount_transferred` is transferred from the members account
//! to the bonded pools account. Then the pool calls `staking::bond_extra(amount_transferred)` and
//! issues new points which are tracked by the member and added to the bonded pool's points.
//! When a member joins a pool, `amount_transferred` is transferred from the members account to the
//! bonded pools account. Then the pool calls `staking::bond_extra(amount_transferred)` and issues
//! new points which are tracked by the member and added to the bonded pool's points.
//!
//! When the pool already has some balance, we want the value of a point before the transfer to
//! equal the value of a point after the transfer. So, when a member joins a bonded pool with a
@@ -148,77 +178,13 @@
//! ### Reward pool
//!
//! When a pool is first bonded it sets up an deterministic, inaccessible account as its reward
//! destination. To track staking rewards we track how the balance of this reward account changes.
//! destination.
//!
//! The reward pool needs to store:
//! The reward pool is not really a pool anymore, as it does not track points anymore. Instead, it
//! tracks, a virtual value called `reward_counter`, among a few other values.
//!
//! * The pool balance at the time of the last payout: `reward_pool.balance`
//! * The total earnings ever at the time of the last payout: `reward_pool.total_earnings`
//! * The total points in the pool at the time of the last payout: `reward_pool.points`
//!
//! And the member needs to store:
//!
//! * The total payouts at the time of the last payout by that member:
//! `member.reward_pool_total_earnings`
//!
//! Before the first reward claim is initiated for a pool, all the above variables are set to zero.
//!
//! When a member initiates a claim, the following happens:
//!
//! 1) Compute the reward pool's total points and the member's virtual points in the reward pool
//! * First `current_total_earnings` is computed (`current_balance` is the free balance of the
//! reward pool at the beginning of these operations.)
//! ```text
//! current_total_earnings =
//! current_balance - reward_pool.balance + pool.total_earnings;
//! ```
//! * Then the `current_points` is computed. Every balance unit that was added to the reward
//! pool since last time recorded means that the `pool.points` is increased by
//! `bonding_pool.total_points`. In other words, for every unit of balance that has been
//! earned by the reward pool, the reward pool points are inflated by `bonded_pool.points`. In
//! effect this allows each, single unit of balance (e.g. planck) to be divvied up pro-rata
//! among members based on points.
//! ```text
//! new_earnings = current_total_earnings - reward_pool.total_earnings;
//! current_points = reward_pool.points + bonding_pool.points * new_earnings;
//! ```
//! * Finally, the`member_virtual_points` are computed: the product of the member's points in
//! the bonding pool and the total inflow of balance units since the last time the member
//! claimed rewards
//! ```text
//! new_earnings_since_last_claim = current_total_earnings - member.reward_pool_total_earnings;
//! member_virtual_points = member.points * new_earnings_since_last_claim;
//! ```
//! 2) Compute the `member_payout`:
//! ```text
//! member_pool_point_ratio = member_virtual_points / current_points;
//! member_payout = current_balance * member_pool_point_ratio;
//! ```
//! 3) Transfer `member_payout` to the member
//! 4) For the member set:
//! ```text
//! member.reward_pool_total_earnings = current_total_earnings;
//! ```
//! 5) For the pool set:
//! ```text
//! reward_pool.points = current_points - member_virtual_points;
//! reward_pool.balance = current_balance - member_payout;
//! reward_pool.total_earnings = current_total_earnings;
//! ```
//!
//! _Note_: One short coming of this design is that new joiners can claim rewards for the era after
//! they join even though their funds did not contribute to the pools vote weight. When a
//! member joins, it's `reward_pool_total_earnings` field is set equal to the `total_earnings`
//! of the reward pool at that point in time. At best the reward pool has the rewards up through the
//! previous era. If a member joins prior to the election snapshot it will benefit from the
//! rewards for the active era despite not contributing to the pool's vote weight. If it joins
//! after the election snapshot is taken it will benefit from the rewards of the next _2_ eras
//! because it's vote weight will not be counted until the election snapshot in active era + 1.
//! Related: <https://github.com/paritytech/substrate/issues/10861>
// _Note to maintainers_: In order to ensure the reward account never falls below the existential
// deposit, at creation the reward account must be endowed with the existential deposit. All logic
// for calculating rewards then does not see that existential deposit as part of the free balance.
// See `RewardPool::current_balance`.
//! See [this link](https://hackmd.io/PFGn6wI5TbCmBYoEA_f2Uw) for an in-depth explanation of the
//! reward pool mechanism.
//!
//! **Relevant extrinsics:**
//!
@@ -297,13 +263,8 @@
//! * PoolMembers cannot vote with their staked funds because they are transferred into the pools
//! account. In the future this can be overcome by allowing the members to vote with their bonded
//! funds via vote splitting.
//! * PoolMembers cannot quickly transfer to another pool if they do not like the nominations,
//! instead they must wait for the unbonding duration.
//!
//! # Runtime builder warnings
//!
//! * Watch out for overflow of [`RewardPoints`] and [`BalanceOf`] types. Consider things like the
//! chains total issuance, staking reward rate, and burn rate.
//! * PoolMembers cannot quickly transfer to another pool if they do no like nominations, instead
//! they must wait for the unbonding duration.
#![cfg_attr(not(feature = "std"), no_std)]
@@ -320,7 +281,10 @@ use frame_support::{
};
use scale_info::TypeInfo;
use sp_core::U256;
use sp_runtime::traits::{AccountIdConversion, Bounded, CheckedSub, Convert, Saturating, Zero};
use sp_runtime::{
traits::{AccountIdConversion, Bounded, CheckedAdd, CheckedSub, Convert, Saturating, Zero},
FixedPointNumber, FixedPointOperand,
};
use sp_staking::{EraIndex, OnStakerSlash, StakingInterface};
use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, ops::Div, vec::Vec};
@@ -352,8 +316,6 @@ pub use weights::WeightInfo;
/// The balance type used by the currency system.
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
/// Type used to track the points of a reward pool.
pub type RewardPoints = U256;
/// Type used for unique identifier of each pool.
pub type PoolId = u32;
@@ -407,26 +369,50 @@ pub struct PoolMember<T: Config> {
/// The quantity of points this member has in the bonded pool or in a sub pool if
/// `Self::unbonding_era` is some.
pub points: BalanceOf<T>,
/// The reward pools total earnings _ever_ the last time this member claimed a payout.
/// Assuming no massive burning events, we expect this value to always be below total issuance.
/// This value lines up with the [`RewardPool::total_earnings`] after a member claims a
/// payout.
pub reward_pool_total_earnings: BalanceOf<T>,
/// The reward counter at the time of this member's last payout claim.
pub last_recorded_reward_counter: T::RewardCounter,
/// The eras in which this member is unbonding, mapped from era index to the number of
/// points scheduled to unbond in the given era.
pub unbonding_eras: BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding>,
}
impl<T: Config> PoolMember<T> {
fn total_points(&self) -> BalanceOf<T> {
self.active_points().saturating_add(self.unbonding_points())
/// The pending rewards of this member.
fn pending_rewards(
&self,
current_reward_counter: T::RewardCounter,
) -> Result<BalanceOf<T>, Error<T>> {
// accuracy note: Reward counters are `FixedU128` with base of 10^18. This value is being
// multiplied by a point. The worse case of a point is 10x the granularity of the balance
// (10x is the common configuration of `MaxPointsToBalance`).
//
// Assuming roughly the current issuance of polkadot (12,047,781,394,999,601,455, which is
// 1.2 * 10^9 * 10^10 = 1.2 * 10^19), the worse case point value is around 10^20.
//
// The final multiplication is:
//
// rc * 10^20 / 10^18 = rc * 100
//
// meaning that as long as reward_counter's value is less than 1/100th of its max capacity
// (u128::MAX_VALUE), `checked_mul_int` won't saturate.
//
// given the nature of reward counter being 'pending_rewards / pool_total_point', the only
// (unrealistic) way that super high values can be achieved is for a pool to suddenly
// receive massive rewards with a very very small amount of stake. In all normal pools, as
// the points increase, so does the rewards. Moreover, as long as rewards are not
// accumulated for astronomically large durations,
// `current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter)`
// won't be extremely big.
(current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter))
.checked_mul_int(self.active_points())
.ok_or(Error::<T>::OverflowRisk)
}
/// Active balance of the member.
///
/// This is derived from the ratio of points in the pool to which the member belongs to.
/// Might return different values based on the pool state for the same member and points.
pub(crate) fn active_balance(&self) -> BalanceOf<T> {
fn active_balance(&self) -> BalanceOf<T> {
if let Some(pool) = BondedPool::<T>::get(self.pool_id).defensive() {
pool.points_to_balance(self.points)
} else {
@@ -434,13 +420,18 @@ impl<T: Config> PoolMember<T> {
}
}
/// Total points of this member, both active and unbonding.
fn total_points(&self) -> BalanceOf<T> {
self.active_points().saturating_add(self.unbonding_points())
}
/// Active points of the member.
pub(crate) fn active_points(&self) -> BalanceOf<T> {
fn active_points(&self) -> BalanceOf<T> {
self.points
}
/// Inactive points of the member, waiting to be withdrawn.
pub(crate) fn unbonding_points(&self) -> BalanceOf<T> {
fn unbonding_points(&self) -> BalanceOf<T> {
self.unbonding_eras
.as_ref()
.iter()
@@ -673,7 +664,7 @@ impl<T: Config> BondedPool<T> {
MaxPoolMembers::<T>::get().map_or(true, |max| PoolMembers::<T>::count() < max),
Error::<T>::MaxPoolMembers
);
self.member_counter = self.member_counter.defensive_saturating_add(1);
self.member_counter = self.member_counter.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
Ok(())
}
@@ -750,19 +741,19 @@ impl<T: Config> BondedPool<T> {
// We checked for zero above
.div(bonded_balance);
let min_points_to_balance = T::MinPointsToBalance::get();
let max_points_to_balance = T::MaxPointsToBalance::get();
// Pool points can inflate relative to balance, but only if the pool is slashed.
// If we cap the ratio of points:balance so one cannot join a pool that has been slashed
// by `min_points_to_balance`%, if not zero.
// by `max_points_to_balance`%, if not zero.
ensure!(
points_to_balance_ratio_floor < min_points_to_balance.into(),
points_to_balance_ratio_floor < max_points_to_balance.into(),
Error::<T>::OverflowRisk
);
// while restricting the balance to `min_points_to_balance` of max total issuance,
// while restricting the balance to `max_points_to_balance` of max total issuance,
let next_bonded_balance = bonded_balance.saturating_add(new_funds);
ensure!(
next_bonded_balance < BalanceOf::<T>::max_value().div(min_points_to_balance.into()),
next_bonded_balance < BalanceOf::<T>::max_value().div(max_points_to_balance.into()),
Error::<T>::OverflowRisk
);
@@ -904,16 +895,6 @@ impl<T: Config> BondedPool<T> {
Ok(points_issued)
}
/// If `n` saturates at it's upper bound, mark the pool as destroying. This is useful when a
/// number saturating indicates the pool can no longer correctly keep track of state.
fn bound_check(&mut self, n: U256) -> U256 {
if n == U256::max_value() {
self.set_state(PoolState::Destroying)
}
n
}
// Set the state of `self`, and deposit an event if the state changed. State should never be set
// directly in in order to ensure a state change event is always correctly deposited.
fn set_state(&mut self, state: PoolState) {
@@ -928,45 +909,104 @@ impl<T: Config> BondedPool<T> {
}
/// A reward pool.
///
/// A reward pool is not so much a pool anymore, since it does not contain any shares or points.
/// Rather, simply to fit nicely next to bonded pool and unbonding pools in terms of terminology. In
/// reality, a reward pool is just a container for a few pool-dependent data related to the rewards.
#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)]
#[cfg_attr(feature = "std", derive(Clone, PartialEq))]
#[cfg_attr(feature = "std", derive(Clone, PartialEq, DefaultNoBound))]
#[codec(mel_bound(T: Config))]
#[scale_info(skip_type_params(T))]
pub struct RewardPool<T: Config> {
/// The balance of this reward pool after the last claimed payout.
pub balance: BalanceOf<T>,
/// The total earnings _ever_ of this reward pool after the last claimed payout. I.E. the sum
/// of all incoming balance through the pools life.
/// The last recorded value of the reward counter.
///
/// NOTE: We assume this will always be less than total issuance and thus can use the runtimes
/// `Balance` type. However in a chain with a burn rate higher than the rate this increases,
/// this type should be bigger than `Balance`.
pub total_earnings: BalanceOf<T>,
/// The total points of this reward pool after the last claimed payout.
pub points: RewardPoints,
/// This is updated ONLY when the points in the bonded pool change, which means `join`,
/// `bond_extra` and `unbond`, all of which is done through `update_recorded`.
last_recorded_reward_counter: T::RewardCounter,
/// The last recorded total payouts of the reward pool.
///
/// Payouts is essentially income of the pool.
///
/// Update criteria is same as that of `last_recorded_reward_counter`.
last_recorded_total_payouts: BalanceOf<T>,
/// Total amount that this pool has paid out so far to the members.
total_rewards_claimed: BalanceOf<T>,
}
impl<T: Config> RewardPool<T> {
/// Mutate the reward pool by updating the total earnings and current free balance.
fn update_total_earnings_and_balance(&mut self, id: PoolId) {
let current_balance = Self::current_balance(id);
// The earnings since the last time it was updated
let new_earnings = current_balance.saturating_sub(self.balance);
// The lifetime earnings of the of the reward pool
self.total_earnings = new_earnings.saturating_add(self.total_earnings);
self.balance = current_balance;
/// Getter for [`RewardPool::last_recorded_reward_counter`].
fn last_recorded_reward_counter(&self) -> T::RewardCounter {
self.last_recorded_reward_counter
}
/// Get a reward pool and update its total earnings and balance
fn get_and_update(id: PoolId) -> Option<Self> {
RewardPools::<T>::get(id).map(|mut r| {
r.update_total_earnings_and_balance(id);
r
})
/// Register some rewards that are claimed from the pool by the members.
fn register_claimed_reward(&mut self, reward: BalanceOf<T>) {
self.total_rewards_claimed = self.total_rewards_claimed.saturating_add(reward);
}
/// The current balance of the reward pool. Never access the reward pools free balance directly.
/// The existential deposit was not received as a reward, so the reward pool can not use it.
/// Update the recorded values of the pool.
fn update_records(&mut self, id: PoolId, bonded_points: BalanceOf<T>) -> Result<(), Error<T>> {
let balance = Self::current_balance(id);
self.last_recorded_reward_counter = self.current_reward_counter(id, bonded_points)?;
self.last_recorded_total_payouts = balance
.checked_add(&self.total_rewards_claimed)
.ok_or(Error::<T>::OverflowRisk)?;
Ok(())
}
/// Get the current reward counter, based on the given `bonded_points` being the state of the
/// bonded pool at this time.
fn current_reward_counter(
&self,
id: PoolId,
bonded_points: BalanceOf<T>,
) -> Result<T::RewardCounter, Error<T>> {
let balance = Self::current_balance(id);
let payouts_since_last_record = balance
.saturating_add(self.total_rewards_claimed)
.saturating_sub(self.last_recorded_total_payouts);
// * accuracy notes regarding the multiplication in `checked_from_rational`:
// `payouts_since_last_record` is a subset of the total_issuance at the very
// worse. `bonded_points` are similarly, in a non-slashed pool, have the same granularity as
// balance, and are thus below within the range of total_issuance. In the worse case
// scenario, for `saturating_from_rational`, we have:
//
// dot_total_issuance * 10^18 / `minJoinBond`
//
// assuming `MinJoinBond == ED`
//
// dot_total_issuance * 10^18 / 10^10 = dot_total_issuance * 10^8
//
// which, with the current numbers, is a miniscule fraction of the u128 capacity.
//
// Thus, adding two values of type reward counter should be safe for ages in a chain like
// Polkadot. The important note here is that `reward_pool.last_recorded_reward_counter` only
// ever accumulates, but its semantics imply that it is less than total_issuance, when
// represented as `FixedU128`, which means it is less than `total_issuance * 10^18`.
//
// * accuracy notes regarding `checked_from_rational` collapsing to zero, meaning that no
// reward can be claimed:
//
// largest `bonded_points`, such that the reward counter is non-zero, with `FixedU128`
// will be when the payout is being computed. This essentially means `payout/bonded_points`
// needs to be more than 1/1^18. Thus, assuming that `bonded_points` will always be less
// than `10 * dot_total_issuance`, if the reward_counter is the smallest possible value,
// the value of the reward being calculated is:
//
// x / 10^20 = 1/ 10^18
//
// x = 100
//
// which is basically 10^-8 DOTs. See `smallest_claimable_reward` for an example of this.
T::RewardCounter::checked_from_rational(payouts_since_last_record, bonded_points)
.and_then(|ref r| self.last_recorded_reward_counter.checked_add(r))
.ok_or(Error::<T>::OverflowRisk)
}
/// Current free balance of the reward pool.
///
/// This is sum of all the rewards that are claimable by pool members.
fn current_balance(id: PoolId) -> BalanceOf<T> {
T::Currency::free_balance(&Pallet::<T>::create_reward_account(id))
.saturating_sub(T::Currency::minimum_balance())
@@ -1098,9 +1138,10 @@ pub mod pallet {
use super::*;
use frame_support::traits::StorageVersion;
use frame_system::{ensure_signed, pallet_prelude::*};
use sp_runtime::traits::CheckedAdd;
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
#[pallet::pallet]
#[pallet::generate_store(pub(crate) trait Store)]
@@ -1116,19 +1157,53 @@ pub mod pallet {
type WeightInfo: weights::WeightInfo;
/// The nominating balance.
type Currency: Currency<Self::AccountId>;
type Currency: Currency<Self::AccountId, Balance = Self::CurrencyBalance>;
/// Sadly needed to bound it to `FixedPointOperand`.
// The only alternative is to sprinkle a `where BalanceOf<T>: FixedPointOperand` in roughly
// a million places, so we prefer doing this.
type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned
+ codec::FullCodec
+ MaybeSerializeDeserialize
+ sp_std::fmt::Debug
+ Default
+ FixedPointOperand
+ CheckedAdd
+ TypeInfo
+ MaxEncodedLen;
/// The type that is used for reward counter.
///
/// The arithmetic of the reward counter might saturate based on the size of the
/// `Currency::Balance`. If this happens, operations fails. Nonetheless, this type should be
/// chosen such that this failure almost never happens, as if it happens, the pool basically
/// needs to be dismantled (or all pools migrated to a larger `RewardCounter` type, which is
/// a PITA to do).
///
/// See the inline code docs of `Member::pending_rewards` and `RewardPool::update_recorded`
/// for example analysis. A [`sp_runtime::FixedU128`] should be fine for chains with balance
/// types similar to that of Polkadot and Kusama, in the absence of severe slashing (or
/// prevented via a reasonable `MaxPointsToBalance`), for many many years to come.
type RewardCounter: FixedPointNumber + MaxEncodedLen + TypeInfo + Default + codec::FullCodec;
/// The nomination pool's pallet id.
#[pallet::constant]
type PalletId: Get<frame_support::PalletId>;
/// The minimum pool points-to-balance ratio that must be maintained for it to be `open`.
/// The maximum pool points-to-balance ratio that an `open` pool can have.
///
/// This is important in the event slashing takes place and the pool's points-to-balance
/// ratio becomes disproportional.
///
/// Moreover, this relates to the `RewardCounter` type as well, as the arithmetic operations
/// are a function of number of points, and by setting this value to e.g. 10, you ensure
/// that the total number of points in the system are at most 10 times the total_issuance of
/// the chain, in the absolute worse case.
///
/// For a value of 10, the threshold would be a pool points-to-balance ratio of 10:1.
/// Such a scenario would also be the equivalent of the pool being 90% slashed.
#[pallet::constant]
type MinPointsToBalance: Get<u32>;
type MaxPointsToBalance: Get<u8>;
/// Infallible method for converting `Currency::Balance` to `U256`.
type BalanceToU256: Convert<BalanceOf<Self>, U256>;
@@ -1427,10 +1502,10 @@ pub mod pallet {
let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
bonded_pool.ok_to_join(amount)?;
// We just need its total earnings at this point in time, but we don't need to write it
// because we are not adjusting its points (all other values can calculated virtual).
let reward_pool = RewardPool::<T>::get_and_update(pool_id)
let mut reward_pool = RewardPools::<T>::get(pool_id)
.defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
// IMPORTANT: reward pool records must be updated with the old points.
reward_pool.update_records(pool_id, bonded_pool.points)?;
bonded_pool.try_inc_members()?;
let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Later)?;
@@ -1440,13 +1515,9 @@ pub mod pallet {
PoolMember::<T> {
pool_id,
points: points_issued,
// At best the reward pool has the rewards up through the previous era. If the
// member joins prior to the snapshot they will benefit from the rewards of
// the active era despite not contributing to the pool's vote weight. If they
// join after the snapshot is taken they will benefit from the rewards of the
// next 2 eras because their vote weight will not be counted until the
// snapshot in active era + 1.
reward_pool_total_earnings: reward_pool.total_earnings,
// we just updated `last_known_reward_counter` to the current one in
// `update_recorded`.
last_recorded_reward_counter: reward_pool.last_recorded_reward_counter(),
unbonding_eras: Default::default(),
},
);
@@ -1457,7 +1528,9 @@ pub mod pallet {
bonded: amount,
joined: true,
});
bonded_pool.put();
RewardPools::<T>::insert(pool_id, reward_pool);
Ok(())
}
@@ -1466,6 +1539,8 @@ pub mod pallet {
///
/// Additional funds can come from either the free balance of the account, of from the
/// accumulated rewards, see [`BondExtra`].
///
/// Bonding extra funds implies an automatic payout of all pending rewards as well.
// NOTE: this transaction is implemented with the sole purpose of readability and
// correctness, not optimization. We read/write several storage items multiple times instead
// of just once, in the spirit reusing code.
@@ -1478,19 +1553,23 @@ pub mod pallet {
let who = ensure_signed(origin)?;
let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?;
// payout related stuff: we must claim the payouts, and updated recorded payout data
// before updating the bonded pool points, similar to that of `join` transaction.
reward_pool.update_records(bonded_pool.id, bonded_pool.points)?;
// TODO: optimize this to not touch the free balance of `who ` at all in benchmarks.
// Currently, bonding rewards is like a batch. In the same PR, also make this function
// take a boolean argument that make it either 100% pure (no storage update), or make it
// also emit event and do the transfer. #11671
let claimed =
Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?;
let (points_issued, bonded) = match extra {
BondExtra::FreeBalance(amount) =>
(bonded_pool.try_bond_funds(&who, amount, BondType::Later)?, amount),
BondExtra::Rewards => {
let claimed = Self::do_reward_payout(
&who,
&mut member,
&mut bonded_pool,
&mut reward_pool,
)?;
(bonded_pool.try_bond_funds(&who, claimed, BondType::Later)?, claimed)
},
BondExtra::Rewards =>
(bonded_pool.try_bond_funds(&who, claimed, BondType::Later)?, claimed),
};
bonded_pool.ok_to_be_open(bonded)?;
member.points = member.points.saturating_add(points_issued);
@@ -1558,21 +1637,17 @@ pub mod pallet {
member_account: T::AccountId,
#[pallet::compact] unbonding_points: BalanceOf<T>,
) -> DispatchResult {
let caller = ensure_signed(origin)?;
let who = ensure_signed(origin)?;
let (mut member, mut bonded_pool, mut reward_pool) =
Self::get_member_with_pools(&member_account)?;
bonded_pool.ok_to_unbond_with(&caller, &member_account, &member, unbonding_points)?;
bonded_pool.ok_to_unbond_with(&who, &member_account, &member, unbonding_points)?;
// Claim the the payout prior to unbonding. Once the user is unbonding their points no
// longer exist in the bonded pool and thus they can no longer claim their payouts. It
// is not strictly necessary to claim the rewards, but we do it here for UX.
Self::do_reward_payout(
&member_account,
&mut member,
&mut bonded_pool,
&mut reward_pool,
)?;
let _ = reward_pool.update_records(bonded_pool.id, bonded_pool.points)?;
let _ = Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?;
let current_era = T::StakingInterface::current_era();
let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era);
@@ -1846,23 +1921,25 @@ pub mod pallet {
PoolMember::<T> {
pool_id,
points,
reward_pool_total_earnings: Zero::zero(),
last_recorded_reward_counter: Zero::zero(),
unbonding_eras: Default::default(),
},
);
RewardPools::<T>::insert(
pool_id,
RewardPool::<T> {
balance: Zero::zero(),
points: U256::zero(),
total_earnings: Zero::zero(),
last_recorded_reward_counter: Zero::zero(),
last_recorded_total_payouts: Zero::zero(),
total_rewards_claimed: Zero::zero(),
},
);
ReversePoolIdLookup::<T>::insert(bonded_pool.bonded_account(), pool_id);
Self::deposit_event(Event::<T>::Created {
depositor: who.clone(),
pool_id: pool_id.clone(),
});
Self::deposit_event(Event::<T>::Bonded {
member: who,
pool_id,
@@ -1896,8 +1973,14 @@ pub mod pallet {
/// 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.
/// If a pool is already in the `Destroying` state, then under no condition can its state
/// change again.
///
/// The dispatch origin of this call must be either:
///
/// 1. signed by the state toggler, or the root role of the pool,
/// 2. if the pool conditions to be open are NOT met (as described by `ok_to_be_open`), and
/// then the state of the pool can be permissionlessly changed to `Destroying`.
#[pallet::weight(T::WeightInfo::set_state())]
#[transactional]
pub fn set_state(
@@ -2065,14 +2148,9 @@ pub mod pallet {
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn integrity_test() {
assert!(
T::MinPointsToBalance::get() > 0,
T::MaxPointsToBalance::get() > 0,
"Minimum points to balance ratio must be greater than 0"
);
assert!(
sp_std::mem::size_of::<RewardPoints>() >=
2 * sp_std::mem::size_of::<BalanceOf<T>>(),
"bit-length of the reward points must be at least twice as much as balance"
);
assert!(
T::StakingInterface::bonding_duration() < TotalUnbondingPools::<T>::get(),
"There must be more unbonding pools then the bonding duration /
@@ -2119,7 +2197,7 @@ impl<T: Config> Pallet<T> {
ExistenceRequirement::AllowDeath,
);
// TODO: this is purely defensive.
// NOTE: this is purely defensive.
T::Currency::make_free_balance_be(&reward_account, Zero::zero());
T::Currency::make_free_balance_be(&bonded_pool.bonded_account(), Zero::zero());
@@ -2212,75 +2290,6 @@ impl<T: Config> Pallet<T> {
.div(current_points)
}
/// Calculate the rewards for `member`.
///
/// Returns the payout amount.
fn calculate_member_payout(
member: &mut PoolMember<T>,
bonded_pool: &mut BondedPool<T>,
reward_pool: &mut RewardPool<T>,
) -> Result<BalanceOf<T>, DispatchError> {
let u256 = |x| T::BalanceToU256::convert(x);
let balance = |x| T::U256ToBalance::convert(x);
let last_total_earnings = reward_pool.total_earnings;
reward_pool.update_total_earnings_and_balance(bonded_pool.id);
// Notice there is an edge case where total_earnings have not increased and this is zero
let new_earnings = u256(reward_pool.total_earnings.saturating_sub(last_total_earnings));
// The new points that will be added to the pool. For every unit of balance that has been
// earned by the reward pool, we inflate the reward pool points by `bonded_pool.points`. In
// effect this allows each, single unit of balance (e.g. plank) to be divvied up pro rata
// among members based on points.
let new_points = u256(bonded_pool.points).saturating_mul(new_earnings);
// The points of the reward pool after taking into account the new earnings. Notice that
// this only stays even or increases over time except for when we subtract member virtual
// shares.
let current_points = bonded_pool.bound_check(reward_pool.points.saturating_add(new_points));
// The rewards pool's earnings since the last time this member claimed a payout.
let new_earnings_since_last_claim =
reward_pool.total_earnings.saturating_sub(member.reward_pool_total_earnings);
// The points of the reward pool that belong to the member.
let member_virtual_points =
// The members portion of the reward pool
u256(member.active_points())
// times the amount the pool has earned since the member last claimed.
.saturating_mul(u256(new_earnings_since_last_claim));
let member_payout = if member_virtual_points.is_zero() ||
current_points.is_zero() ||
reward_pool.balance.is_zero()
{
Zero::zero()
} else {
// Equivalent to `(member_virtual_points / current_points) * reward_pool.balance`
let numerator = {
let numerator = member_virtual_points.saturating_mul(u256(reward_pool.balance));
bonded_pool.bound_check(numerator)
};
balance(
numerator
// We check for zero above
.div(current_points),
)
};
// Record updates
if reward_pool.total_earnings == BalanceOf::<T>::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);
Ok(member_payout)
}
/// If the member has some rewards, transfer a payout from the reward pool to the member.
// Emits events and potentially modifies pool state if any arithmetic saturates, but does
// not persist any of the mutable inputs to storage.
@@ -2291,38 +2300,37 @@ impl<T: Config> Pallet<T> {
reward_pool: &mut RewardPool<T>,
) -> Result<BalanceOf<T>, DispatchError> {
debug_assert_eq!(member.pool_id, bonded_pool.id);
// a member who has no skin in the game anymore cannot claim any rewards.
ensure!(!member.active_points().is_zero(), Error::<T>::FullyUnbonding);
let was_destroying = bonded_pool.is_destroying();
let member_payout = Self::calculate_member_payout(member, bonded_pool, reward_pool)?;
let current_reward_counter =
reward_pool.current_reward_counter(bonded_pool.id, bonded_pool.points)?;
let pending_rewards = member.pending_rewards(current_reward_counter)?;
if member_payout.is_zero() {
return Ok(member_payout)
if pending_rewards.is_zero() {
return Ok(pending_rewards)
}
// IFF the reward is non-zero alter the member and reward pool info.
member.last_recorded_reward_counter = current_reward_counter;
reward_pool.register_claimed_reward(pending_rewards);
// Transfer payout to the member.
T::Currency::transfer(
&bonded_pool.reward_account(),
&member_account,
member_payout,
pending_rewards,
ExistenceRequirement::AllowDeath,
)?;
Self::deposit_event(Event::<T>::PaidOut {
member: member_account.clone(),
pool_id: member.pool_id,
payout: member_payout,
payout: pending_rewards,
});
if bonded_pool.is_destroying() && !was_destroying {
Self::deposit_event(Event::<T>::StateChanged {
pool_id: member.pool_id,
new_state: PoolState::Destroying,
});
}
Ok(member_payout)
Ok(pending_rewards)
}
/// Ensure the correctness of the state of this pallet.
@@ -16,11 +16,12 @@
// limitations under the License.
use super::*;
use crate::log;
use frame_support::traits::OnRuntimeUpgrade;
use sp_std::collections::btree_map::BTreeMap;
pub mod v1 {
use super::*;
use crate::log;
use frame_support::traits::OnRuntimeUpgrade;
#[derive(Decode)]
pub struct OldPoolRoles<AccountId> {
@@ -103,3 +104,282 @@ pub mod v1 {
}
}
}
pub mod v2 {
use super::*;
use sp_runtime::Perbill;
#[test]
fn migration_assumption_is_correct() {
// this migrations cleans all the reward accounts to contain exactly ed, and all members
// having no claimable rewards. In this state, all fields of the `RewardPool` and
// `member.last_recorded_reward_counter` are all zero.
use crate::mock::*;
ExtBuilder::default().build_and_execute(|| {
let join = |x| {
Balances::make_free_balance_be(&x, Balances::minimum_balance() + 10);
frame_support::assert_ok!(Pools::join(Origin::signed(x), 10, 1));
};
assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 10);
assert_eq!(
RewardPools::<Runtime>::get(1).unwrap(),
RewardPool { ..Default::default() }
);
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap().last_recorded_reward_counter,
Zero::zero()
);
join(20);
assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 20);
assert_eq!(
RewardPools::<Runtime>::get(1).unwrap(),
RewardPool { ..Default::default() }
);
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap().last_recorded_reward_counter,
Zero::zero()
);
assert_eq!(
PoolMembers::<Runtime>::get(20).unwrap().last_recorded_reward_counter,
Zero::zero()
);
join(30);
assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 30);
assert_eq!(
RewardPools::<Runtime>::get(1).unwrap(),
RewardPool { ..Default::default() }
);
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap().last_recorded_reward_counter,
Zero::zero()
);
assert_eq!(
PoolMembers::<Runtime>::get(20).unwrap().last_recorded_reward_counter,
Zero::zero()
);
assert_eq!(
PoolMembers::<Runtime>::get(30).unwrap().last_recorded_reward_counter,
Zero::zero()
);
});
}
#[derive(Decode)]
pub struct OldRewardPool<B> {
pub balance: B,
pub total_earnings: B,
pub points: U256,
}
#[derive(Decode)]
pub struct OldPoolMember<T: Config> {
pub pool_id: PoolId,
pub points: BalanceOf<T>,
pub reward_pool_total_earnings: BalanceOf<T>,
pub unbonding_eras: BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding>,
}
/// Migrate the pool reward scheme to the new version, as per
/// <https://github.com/paritytech/substrate/pull/11669.>.
pub struct MigrateToV2<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> MigrateToV2<T> {
fn run(current: StorageVersion) -> Weight {
let mut reward_pools_translated = 0u64;
let mut members_translated = 0u64;
// just for logging.
let mut total_value_locked = BalanceOf::<T>::zero();
let mut total_points_locked = BalanceOf::<T>::zero();
// store each member of the pool, with their active points. In the process, migrate
// their data as well.
let mut temp_members = BTreeMap::<PoolId, Vec<(T::AccountId, BalanceOf<T>)>>::new();
PoolMembers::<T>::translate::<OldPoolMember<T>, _>(|key, old_member| {
let id = old_member.pool_id;
temp_members.entry(id).or_default().push((key, old_member.points));
total_points_locked += old_member.points;
members_translated += 1;
Some(PoolMember::<T> {
last_recorded_reward_counter: Zero::zero(),
pool_id: old_member.pool_id,
points: old_member.points,
unbonding_eras: old_member.unbonding_eras,
})
});
// translate all reward pools. In the process, do the last payout as well.
RewardPools::<T>::translate::<OldRewardPool<BalanceOf<T>>, _>(
|id, _old_reward_pool| {
// each pool should have at least one member.
let members = match temp_members.get(&id) {
Some(x) => x,
None => {
log!(error, "pool {} has no member! deleting it..", id);
return None
},
};
let bonded_pool = match BondedPools::<T>::get(id) {
Some(x) => x,
None => {
log!(error, "pool {} has no bonded pool! deleting it..", id);
return None
},
};
let accumulated_reward = RewardPool::<T>::current_balance(id);
let reward_account = Pallet::<T>::create_reward_account(id);
let mut sum_paid_out = BalanceOf::<T>::zero();
members
.into_iter()
.filter_map(|(who, points)| {
let bonded_pool = match BondedPool::<T>::get(id) {
Some(x) => x,
None => {
log!(error, "pool {} for member {:?} does not exist!", id, who);
return None
},
};
total_value_locked += bonded_pool.points_to_balance(points.clone());
let portion = Perbill::from_rational(*points, bonded_pool.points);
let last_claim = portion * accumulated_reward;
log!(
debug,
"{:?} has {:?} ({:?}) of pool {} with total reward of {:?}",
who,
portion,
last_claim,
id,
accumulated_reward
);
if last_claim.is_zero() {
None
} else {
Some((who, last_claim))
}
})
.for_each(|(who, last_claim)| {
let outcome = T::Currency::transfer(
&reward_account,
&who,
last_claim,
ExistenceRequirement::KeepAlive,
);
if let Err(reason) = outcome {
log!(warn, "last reward claim failed due to {:?}", reason,);
} else {
sum_paid_out = sum_paid_out.saturating_add(last_claim);
}
Pallet::<T>::deposit_event(Event::<T>::PaidOut {
member: who.clone(),
pool_id: id,
payout: last_claim,
});
});
// this can only be because of rounding down, or because the person we
// wanted to pay their reward to could not accept it (dust).
let leftover = accumulated_reward.saturating_sub(sum_paid_out);
if !leftover.is_zero() {
// pay it all to depositor.
let o = T::Currency::transfer(
&reward_account,
&bonded_pool.roles.depositor,
leftover,
ExistenceRequirement::KeepAlive,
);
log!(warn, "paying {:?} leftover to the depositor: {:?}", leftover, o);
}
// finally, migrate the reward pool.
reward_pools_translated += 1;
Some(RewardPool {
last_recorded_reward_counter: Zero::zero(),
last_recorded_total_payouts: Zero::zero(),
total_rewards_claimed: Zero::zero(),
})
},
);
log!(
info,
"Upgraded {} members, {} reward pools, TVL {:?} TPL {:?}, storage to version {:?}",
members_translated,
reward_pools_translated,
total_value_locked,
total_points_locked,
current
);
current.put::<Pallet<T>>();
T::DbWeight::get().reads_writes(members_translated + 1, reward_pools_translated + 1)
}
}
impl<T: Config> OnRuntimeUpgrade for MigrateToV2<T> {
fn on_runtime_upgrade() -> Weight {
let current = Pallet::<T>::current_storage_version();
let onchain = Pallet::<T>::on_chain_storage_version();
log!(
info,
"Running migration with current storage version {:?} / onchain {:?}",
current,
onchain
);
if current == 2 && onchain == 1 {
Self::run(current)
} else {
log!(info, "MigrateToV2 did not executed. This probably should be removed");
T::DbWeight::get().reads(1)
}
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<(), &'static str> {
// all reward accounts must have more than ED.
RewardPools::<T>::iter().for_each(|(id, _)| {
assert!(
T::Currency::free_balance(&Pallet::<T>::create_reward_account(id)) >=
T::Currency::minimum_balance()
)
});
Ok(())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade() -> Result<(), &'static str> {
// new version must be set.
assert_eq!(Pallet::<T>::on_chain_storage_version(), 2);
// no reward or bonded pool has been skipped.
assert_eq!(RewardPools::<T>::iter().count() as u32, RewardPools::<T>::count());
assert_eq!(BondedPools::<T>::iter().count() as u32, BondedPools::<T>::count());
// all reward pools must have exactly ED in them. This means no reward can be claimed,
// and that setting reward counters all over the board to zero will work henceforth.
RewardPools::<T>::iter().for_each(|(id, _)| {
assert_eq!(
RewardPool::<T>::current_balance(id),
Zero::zero(),
"reward pool({}) balance is {:?}",
id,
RewardPool::<T>::current_balance(id)
);
});
log!(info, "post upgrade hook for MigrateToV2 executed.");
Ok(())
}
}
}
+41 -11
View File
@@ -2,9 +2,14 @@ use super::*;
use crate::{self as pools};
use frame_support::{assert_ok, parameter_types, PalletId};
use frame_system::RawOrigin;
use sp_runtime::FixedU128;
pub type AccountId = u128;
pub type Balance = u128;
pub type RewardCounter = FixedU128;
// This sneaky little hack allows us to write code exactly as we would do in the pallet in the tests
// as well, e.g. `StorageItem::<T>::get()`.
pub type T = Runtime;
// Ext builder creates a pool with id 1.
pub fn default_bonded_account() -> AccountId {
@@ -23,6 +28,7 @@ parameter_types! {
pub storage UnbondingBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
#[derive(Clone, PartialEq)]
pub static MaxUnbonding: u32 = 8;
pub static StakingMinBond: Balance = 10;
pub storage Nominations: Option<Vec<AccountId>> = None;
}
@@ -40,7 +46,7 @@ impl sp_staking::StakingInterface for StakingMock {
type AccountId = AccountId;
fn minimum_bond() -> Self::Balance {
10
StakingMinBond::get()
}
fn current_era() -> EraIndex {
@@ -184,6 +190,8 @@ impl pools::Config for Runtime {
type Event = Event;
type WeightInfo = ();
type Currency = Balances;
type CurrencyBalance = Balance;
type RewardCounter = RewardCounter;
type BalanceToU256 = BalanceToU256;
type U256ToBalance = U256ToBalance;
type StakingInterface = StakingMock;
@@ -191,7 +199,7 @@ impl pools::Config for Runtime {
type PalletId = PoolsPalletId;
type MaxMetadataLen = MaxMetadataLen;
type MaxUnbonding = MaxUnbonding;
type MinPointsToBalance = frame_support::traits::ConstU32<10>;
type MaxPointsToBalance = frame_support::traits::ConstU8<10>;
}
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Runtime>;
@@ -208,9 +216,16 @@ frame_support::construct_runtime!(
}
);
#[derive(Default)]
pub struct ExtBuilder {
members: Vec<(AccountId, Balance)>,
max_members: Option<u32>,
max_members_per_pool: Option<u32>,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self { members: Default::default(), max_members: Some(4), max_members_per_pool: Some(3) }
}
}
impl ExtBuilder {
@@ -225,11 +240,26 @@ impl ExtBuilder {
self
}
pub(crate) fn min_bond(self, min: Balance) -> Self {
StakingMinBond::set(min);
self
}
pub(crate) fn with_check(self, level: u8) -> Self {
CheckLevel::set(level);
self
}
pub(crate) fn max_members(mut self, max: Option<u32>) -> Self {
self.max_members = max;
self
}
pub(crate) fn max_members_per_pool(mut self, max: Option<u32>) -> Self {
self.max_members_per_pool = max;
self
}
pub(crate) fn build(self) -> sp_io::TestExternalities {
let mut storage =
frame_system::GenesisConfig::default().build_storage::<Runtime>().unwrap();
@@ -238,8 +268,8 @@ impl ExtBuilder {
min_join_bond: 2,
min_create_bond: 2,
max_pools: Some(2),
max_members_per_pool: Some(3),
max_members: Some(4),
max_members_per_pool: self.max_members_per_pool,
max_members: self.max_members,
}
.assimilate_storage(&mut storage);
@@ -281,8 +311,8 @@ pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) -> Result<(),
}
parameter_types! {
static PoolsEvents: usize = 0;
static BalancesEvents: usize = 0;
storage PoolsEvents: u32 = 0;
storage BalancesEvents: u32 = 0;
}
/// All events of this pallet.
@@ -293,8 +323,8 @@ pub(crate) fn pool_events_since_last_call() -> Vec<super::Event<Runtime>> {
.filter_map(|e| if let Event::Pools(inner) = e { Some(inner) } else { None })
.collect::<Vec<_>>();
let already_seen = PoolsEvents::get();
PoolsEvents::set(events.len());
events.into_iter().skip(already_seen).collect()
PoolsEvents::set(&(events.len() as u32));
events.into_iter().skip(already_seen as usize).collect()
}
/// All events of the `Balances` pallet.
@@ -305,8 +335,8 @@ pub(crate) fn balances_events_since_last_call() -> Vec<pallet_balances::Event<Ru
.filter_map(|e| if let Event::Balances(inner) = e { Some(inner) } else { None })
.collect::<Vec<_>>();
let already_seen = BalancesEvents::get();
BalancesEvents::set(events.len());
events.into_iter().skip(already_seen).collect()
BalancesEvents::set(&(events.len() as u32));
events.into_iter().skip(already_seen as usize).collect()
}
/// Same as `fully_unbond`, in permissioned setting.
File diff suppressed because it is too large Load Diff
+49 -49
View File
@@ -18,7 +18,7 @@
//! Autogenerated weights for pallet_nomination_pools
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2022-06-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2022-06-15, 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
@@ -70,7 +70,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: NominationPools PoolMembers (r:1 w:1)
// Storage: NominationPools BondedPools (r:1 w:1)
// Storage: Staking Ledger (r:1 w:1)
// Storage: NominationPools RewardPools (r:1 w:0)
// Storage: NominationPools RewardPools (r:1 w:1)
// Storage: System Account (r:2 w:1)
// Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0)
// Storage: NominationPools MaxPoolMembers (r:1 w:0)
@@ -80,22 +80,22 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: BagsList ListNodes (r:3 w:3)
// Storage: BagsList ListBags (r:2 w:2)
fn join() -> Weight {
(124_508_000 as Weight)
(123_947_000 as Weight)
.saturating_add(T::DbWeight::get().reads(17 as Weight))
.saturating_add(T::DbWeight::get().writes(11 as Weight))
.saturating_add(T::DbWeight::get().writes(12 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:2 w:2)
// Storage: System Account (r:3 w:2)
// Storage: Staking Ledger (r:1 w:1)
// Storage: Staking Bonded (r:1 w:0)
// Storage: Balances Locks (r:1 w:1)
// Storage: BagsList ListNodes (r:3 w:3)
// Storage: BagsList ListBags (r:2 w:2)
fn bond_extra_transfer() -> Weight {
(115_185_000 as Weight)
.saturating_add(T::DbWeight::get().reads(13 as Weight))
(118_236_000 as Weight)
.saturating_add(T::DbWeight::get().reads(14 as Weight))
.saturating_add(T::DbWeight::get().writes(12 as Weight))
}
// Storage: NominationPools PoolMembers (r:1 w:1)
@@ -108,7 +108,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: BagsList ListNodes (r:3 w:3)
// Storage: BagsList ListBags (r:2 w:2)
fn bond_extra_reward() -> Weight {
(132_723_000 as Weight)
(132_475_000 as Weight)
.saturating_add(T::DbWeight::get().reads(14 as Weight))
.saturating_add(T::DbWeight::get().writes(13 as Weight))
}
@@ -117,7 +117,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: NominationPools RewardPools (r:1 w:1)
// Storage: System Account (r:1 w:1)
fn claim_payout() -> Weight {
(52_498_000 as Weight)
(50_299_000 as Weight)
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().writes(4 as Weight))
}
@@ -136,7 +136,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: NominationPools SubPoolsStorage (r:1 w:1)
// Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1)
fn unbond() -> Weight {
(121_645_000 as Weight)
(121_254_000 as Weight)
.saturating_add(T::DbWeight::get().reads(18 as Weight))
.saturating_add(T::DbWeight::get().writes(13 as Weight))
}
@@ -146,9 +146,9 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: Balances Locks (r:1 w:1)
/// The range of component `s` is `[0, 100]`.
fn pool_withdraw_unbonded(s: u32, ) -> Weight {
(43_320_000 as Weight)
(41_928_000 as Weight)
// Standard Error: 0
.saturating_add((49_000 as Weight).saturating_mul(s as Weight))
.saturating_add((52_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))
}
@@ -162,9 +162,9 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: NominationPools CounterForPoolMembers (r:1 w:1)
/// The range of component `s` is `[0, 100]`.
fn withdraw_unbonded_update(s: u32, ) -> Weight {
(83_195_000 as Weight)
// Standard Error: 5_000
.saturating_add((57_000 as Weight).saturating_mul(s as Weight))
(81_611_000 as Weight)
// Standard Error: 1_000
.saturating_add((56_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))
}
@@ -189,7 +189,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: Staking Payee (r:0 w:1)
/// The range of component `s` is `[0, 100]`.
fn withdraw_unbonded_kill(_s: u32, ) -> Weight {
(143_495_000 as Weight)
(139_849_000 as Weight)
.saturating_add(T::DbWeight::get().reads(19 as Weight))
.saturating_add(T::DbWeight::get().writes(16 as Weight))
}
@@ -216,7 +216,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: NominationPools BondedPools (r:1 w:1)
// Storage: Staking Payee (r:0 w:1)
fn create() -> Weight {
(127_998_000 as Weight)
(126_246_000 as Weight)
.saturating_add(T::DbWeight::get().reads(22 as Weight))
.saturating_add(T::DbWeight::get().writes(15 as Weight))
}
@@ -234,9 +234,9 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: Staking CounterForNominators (r:1 w:1)
/// The range of component `n` is `[1, 16]`.
fn nominate(n: u32, ) -> Weight {
(49_929_000 as Weight)
// Standard Error: 16_000
.saturating_add((2_319_000 as Weight).saturating_mul(n as Weight))
(48_829_000 as Weight)
// Standard Error: 10_000
.saturating_add((2_204_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))
@@ -244,7 +244,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: NominationPools BondedPools (r:1 w:1)
// Storage: Staking Ledger (r:1 w:0)
fn set_state() -> Weight {
(27_399_000 as Weight)
(26_761_000 as Weight)
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
@@ -253,7 +253,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: NominationPools CounterForMetadata (r:1 w:1)
/// The range of component `n` is `[1, 256]`.
fn set_metadata(n: u32, ) -> Weight {
(14_813_000 as Weight)
(14_519_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))
@@ -265,12 +265,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: NominationPools MinCreateBond (r:0 w:1)
// Storage: NominationPools MaxPools (r:0 w:1)
fn set_configs() -> Weight {
(6_115_000 as Weight)
(6_173_000 as Weight)
.saturating_add(T::DbWeight::get().writes(5 as Weight))
}
// Storage: NominationPools BondedPools (r:1 w:1)
fn update_roles() -> Weight {
(22_546_000 as Weight)
(22_261_000 as Weight)
.saturating_add(T::DbWeight::get().reads(1 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
@@ -283,7 +283,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: BagsList ListBags (r:1 w:1)
// Storage: BagsList CounterForListNodes (r:1 w:1)
fn chill() -> Weight {
(48_243_000 as Weight)
(47_959_000 as Weight)
.saturating_add(T::DbWeight::get().reads(8 as Weight))
.saturating_add(T::DbWeight::get().writes(5 as Weight))
}
@@ -295,7 +295,7 @@ impl WeightInfo for () {
// Storage: NominationPools PoolMembers (r:1 w:1)
// Storage: NominationPools BondedPools (r:1 w:1)
// Storage: Staking Ledger (r:1 w:1)
// Storage: NominationPools RewardPools (r:1 w:0)
// Storage: NominationPools RewardPools (r:1 w:1)
// Storage: System Account (r:2 w:1)
// Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0)
// Storage: NominationPools MaxPoolMembers (r:1 w:0)
@@ -305,22 +305,22 @@ impl WeightInfo for () {
// Storage: BagsList ListNodes (r:3 w:3)
// Storage: BagsList ListBags (r:2 w:2)
fn join() -> Weight {
(124_508_000 as Weight)
(123_947_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(17 as Weight))
.saturating_add(RocksDbWeight::get().writes(11 as Weight))
.saturating_add(RocksDbWeight::get().writes(12 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:2 w:2)
// Storage: System Account (r:3 w:2)
// Storage: Staking Ledger (r:1 w:1)
// Storage: Staking Bonded (r:1 w:0)
// Storage: Balances Locks (r:1 w:1)
// Storage: BagsList ListNodes (r:3 w:3)
// Storage: BagsList ListBags (r:2 w:2)
fn bond_extra_transfer() -> Weight {
(115_185_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(13 as Weight))
(118_236_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(14 as Weight))
.saturating_add(RocksDbWeight::get().writes(12 as Weight))
}
// Storage: NominationPools PoolMembers (r:1 w:1)
@@ -333,7 +333,7 @@ impl WeightInfo for () {
// Storage: BagsList ListNodes (r:3 w:3)
// Storage: BagsList ListBags (r:2 w:2)
fn bond_extra_reward() -> Weight {
(132_723_000 as Weight)
(132_475_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(14 as Weight))
.saturating_add(RocksDbWeight::get().writes(13 as Weight))
}
@@ -342,7 +342,7 @@ impl WeightInfo for () {
// Storage: NominationPools RewardPools (r:1 w:1)
// Storage: System Account (r:1 w:1)
fn claim_payout() -> Weight {
(52_498_000 as Weight)
(50_299_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
}
@@ -361,7 +361,7 @@ impl WeightInfo for () {
// Storage: NominationPools SubPoolsStorage (r:1 w:1)
// Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1)
fn unbond() -> Weight {
(121_645_000 as Weight)
(121_254_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(18 as Weight))
.saturating_add(RocksDbWeight::get().writes(13 as Weight))
}
@@ -371,9 +371,9 @@ impl WeightInfo for () {
// Storage: Balances Locks (r:1 w:1)
/// The range of component `s` is `[0, 100]`.
fn pool_withdraw_unbonded(s: u32, ) -> Weight {
(43_320_000 as Weight)
(41_928_000 as Weight)
// Standard Error: 0
.saturating_add((49_000 as Weight).saturating_mul(s as Weight))
.saturating_add((52_000 as Weight).saturating_mul(s as Weight))
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
}
@@ -387,9 +387,9 @@ impl WeightInfo for () {
// Storage: NominationPools CounterForPoolMembers (r:1 w:1)
/// The range of component `s` is `[0, 100]`.
fn withdraw_unbonded_update(s: u32, ) -> Weight {
(83_195_000 as Weight)
// Standard Error: 5_000
.saturating_add((57_000 as Weight).saturating_mul(s as Weight))
(81_611_000 as Weight)
// Standard Error: 1_000
.saturating_add((56_000 as Weight).saturating_mul(s as Weight))
.saturating_add(RocksDbWeight::get().reads(8 as Weight))
.saturating_add(RocksDbWeight::get().writes(7 as Weight))
}
@@ -414,7 +414,7 @@ impl WeightInfo for () {
// Storage: Staking Payee (r:0 w:1)
/// The range of component `s` is `[0, 100]`.
fn withdraw_unbonded_kill(_s: u32, ) -> Weight {
(143_495_000 as Weight)
(139_849_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(19 as Weight))
.saturating_add(RocksDbWeight::get().writes(16 as Weight))
}
@@ -441,7 +441,7 @@ impl WeightInfo for () {
// Storage: NominationPools BondedPools (r:1 w:1)
// Storage: Staking Payee (r:0 w:1)
fn create() -> Weight {
(127_998_000 as Weight)
(126_246_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(22 as Weight))
.saturating_add(RocksDbWeight::get().writes(15 as Weight))
}
@@ -459,9 +459,9 @@ impl WeightInfo for () {
// Storage: Staking CounterForNominators (r:1 w:1)
/// The range of component `n` is `[1, 16]`.
fn nominate(n: u32, ) -> Weight {
(49_929_000 as Weight)
// Standard Error: 16_000
.saturating_add((2_319_000 as Weight).saturating_mul(n as Weight))
(48_829_000 as Weight)
// Standard Error: 10_000
.saturating_add((2_204_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))
@@ -469,7 +469,7 @@ impl WeightInfo for () {
// Storage: NominationPools BondedPools (r:1 w:1)
// Storage: Staking Ledger (r:1 w:0)
fn set_state() -> Weight {
(27_399_000 as Weight)
(26_761_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
@@ -478,7 +478,7 @@ impl WeightInfo for () {
// Storage: NominationPools CounterForMetadata (r:1 w:1)
/// The range of component `n` is `[1, 256]`.
fn set_metadata(n: u32, ) -> Weight {
(14_813_000 as Weight)
(14_519_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))
@@ -490,12 +490,12 @@ impl WeightInfo for () {
// Storage: NominationPools MinCreateBond (r:0 w:1)
// Storage: NominationPools MaxPools (r:0 w:1)
fn set_configs() -> Weight {
(6_115_000 as Weight)
(6_173_000 as Weight)
.saturating_add(RocksDbWeight::get().writes(5 as Weight))
}
// Storage: NominationPools BondedPools (r:1 w:1)
fn update_roles() -> Weight {
(22_546_000 as Weight)
(22_261_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
@@ -508,7 +508,7 @@ impl WeightInfo for () {
// Storage: BagsList ListBags (r:1 w:1)
// Storage: BagsList CounterForListNodes (r:1 w:1)
fn chill() -> Weight {
(48_243_000 as Weight)
(47_959_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(8 as Weight))
.saturating_add(RocksDbWeight::get().writes(5 as Weight))
}
@@ -26,6 +26,7 @@ use pallet_nomination_pools::{
PoolState,
};
use pallet_staking::{CurrentEra, Event as StakingEvent, Payee, RewardDestination};
use sp_runtime::traits::Zero;
#[test]
fn pool_lifecycle_e2e() {
@@ -296,7 +297,7 @@ fn pool_slash_e2e() {
PoolMember {
pool_id: 1,
points: 0,
reward_pool_total_earnings: 0,
last_recorded_reward_counter: Zero::zero(),
// the 10 points unlocked just now correspond to 5 points in the unbond pool.
unbonding_eras: bounded_btree_map!(5 => 10, 6 => 5)
}
@@ -351,7 +352,7 @@ fn pool_slash_e2e() {
PoolMember {
pool_id: 1,
points: 0,
reward_pool_total_earnings: 0,
last_recorded_reward_counter: Zero::zero(),
unbonding_eras: bounded_btree_map!(4 => 10, 5 => 10, 9 => 10)
}
);
@@ -16,8 +16,17 @@
// 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};
use frame_support::{
assert_ok,
pallet_prelude::*,
parameter_types,
traits::{ConstU64, ConstU8},
PalletId,
};
use sp_runtime::{
traits::{Convert, IdentityLookup},
FixedU128,
};
type AccountId = u128;
type AccountIndex = u32;
@@ -159,13 +168,15 @@ impl pallet_nomination_pools::Config for Runtime {
type Event = Event;
type WeightInfo = ();
type Currency = Balances;
type CurrencyBalance = Balance;
type RewardCounter = FixedU128;
type BalanceToU256 = BalanceToU256;
type U256ToBalance = U256ToBalance;
type StakingInterface = Staking;
type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
type MaxMetadataLen = ConstU32<256>;
type MaxUnbonding = ConstU32<8>;
type MinPointsToBalance = ConstU32<10>;
type MaxPointsToBalance = ConstU8<10>;
type PalletId = PoolsPalletId;
}
@@ -472,9 +472,10 @@ pub enum State {
#[clap(short, long)]
snapshot_path: Option<PathBuf>,
/// The pallets to scrape. If empty, entire chain state will be scraped.
/// A pallet to scrape. Can be provided multiple times. If empty, entire chain state will
/// be scraped.
#[clap(short, long, multiple_values = true)]
pallets: Vec<String>,
pallet: Vec<String>,
/// Fetch the child-keys as well.
///
@@ -498,7 +499,7 @@ impl State {
Builder::<Block>::new().mode(Mode::Offline(OfflineConfig {
state_snapshot: SnapshotConfig::new(snapshot_path),
})),
State::Live { snapshot_path, pallets, uri, at, child_tree } => {
State::Live { snapshot_path, pallet, uri, at, child_tree } => {
let at = match at {
Some(at_str) => Some(hash_of::<Block>(at_str)?),
None => None,
@@ -507,7 +508,7 @@ impl State {
.mode(Mode::Online(OnlineConfig {
transport: uri.to_owned().into(),
state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new),
pallets: pallets.clone(),
pallets: pallet.clone(),
scrape_children: true,
at,
}))