mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 01:51:09 +00:00
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:
@@ -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 {}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user