mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-30 06:08:00 +00:00
Allow nomination pools to chill + fix dismantle scenario (#11426)
* 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 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:
Generated
+24
@@ -5931,6 +5931,30 @@ dependencies = [
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-nomination-pools-test-staking"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"frame-election-provider-support",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"log",
|
||||
"pallet-bags-list",
|
||||
"pallet-balances",
|
||||
"pallet-nomination-pools",
|
||||
"pallet-staking",
|
||||
"pallet-staking-reward-curve",
|
||||
"pallet-timestamp",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"sp-core",
|
||||
"sp-io",
|
||||
"sp-runtime",
|
||||
"sp-staking",
|
||||
"sp-std",
|
||||
"sp-tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-offences"
|
||||
version = "4.0.0-dev"
|
||||
|
||||
@@ -115,6 +115,7 @@ members = [
|
||||
"frame/proxy",
|
||||
"frame/nomination-pools",
|
||||
"frame/nomination-pools/benchmarking",
|
||||
"frame/nomination-pools/test-staking",
|
||||
"frame/randomness-collective-flip",
|
||||
"frame/ranked-collective",
|
||||
"frame/recovery",
|
||||
|
||||
@@ -23,8 +23,9 @@ use hex_literal::hex;
|
||||
use node_runtime::{
|
||||
constants::currency::*, wasm_binary_unwrap, AuthorityDiscoveryConfig, BabeConfig,
|
||||
BalancesConfig, Block, CouncilConfig, DemocracyConfig, ElectionsConfig, GrandpaConfig,
|
||||
ImOnlineConfig, IndicesConfig, MaxNominations, SessionConfig, SessionKeys, SocietyConfig,
|
||||
StakerStatus, StakingConfig, SudoConfig, SystemConfig, TechnicalCommitteeConfig,
|
||||
ImOnlineConfig, IndicesConfig, MaxNominations, NominationPoolsConfig, SessionConfig,
|
||||
SessionKeys, SocietyConfig, StakerStatus, StakingConfig, SudoConfig, SystemConfig,
|
||||
TechnicalCommitteeConfig,
|
||||
};
|
||||
use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
|
||||
use sc_chain_spec::ChainSpecExtension;
|
||||
@@ -365,7 +366,11 @@ pub fn testnet_genesis(
|
||||
transaction_payment: Default::default(),
|
||||
alliance: Default::default(),
|
||||
alliance_motion: Default::default(),
|
||||
nomination_pools: Default::default(),
|
||||
nomination_pools: NominationPoolsConfig {
|
||||
min_create_bond: 10 * DOLLARS,
|
||||
min_join_bond: 1 * DOLLARS,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primit
|
||||
sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" }
|
||||
sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" }
|
||||
sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" }
|
||||
sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" }
|
||||
log = { version = "0.4.0", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-balances = { version = "4.0.0-dev", path = "../balances" }
|
||||
sp-io = { version = "6.0.0", path = "../../primitives/io" }
|
||||
sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" }
|
||||
|
||||
[features]
|
||||
@@ -41,6 +41,7 @@ std = [
|
||||
"frame-system/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
"sp-io/std",
|
||||
"sp-staking/std",
|
||||
"sp-core/std",
|
||||
"log/std",
|
||||
|
||||
@@ -24,7 +24,7 @@ mod mock;
|
||||
|
||||
use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelist_account, Vec};
|
||||
use frame_election_provider_support::SortedListProvider;
|
||||
use frame_support::{ensure, traits::Get};
|
||||
use frame_support::{assert_ok, ensure, traits::Get};
|
||||
use frame_system::RawOrigin as Origin;
|
||||
use pallet_nomination_pools::{
|
||||
BalanceOf, BondExtra, BondedPoolInner, BondedPools, ConfigOp, MaxPoolMembers,
|
||||
@@ -48,6 +48,12 @@ pub trait Config:
|
||||
|
||||
pub struct Pallet<T: Config>(Pools<T>);
|
||||
|
||||
fn min_create_bond<T: Config>() -> BalanceOf<T> {
|
||||
MinCreateBond::<T>::get()
|
||||
.max(T::StakingInterface::minimum_bond())
|
||||
.max(CurrencyOf::<T>::minimum_balance())
|
||||
}
|
||||
|
||||
fn create_funded_user_with_balance<T: pallet_nomination_pools::Config>(
|
||||
string: &'static str,
|
||||
n: u32,
|
||||
@@ -209,9 +215,7 @@ impl<T: Config> ListScenario<T> {
|
||||
|
||||
frame_benchmarking::benchmarks! {
|
||||
join {
|
||||
let origin_weight = pallet_nomination_pools::MinCreateBond::<T>::get()
|
||||
.max(CurrencyOf::<T>::minimum_balance())
|
||||
* 2u32.into();
|
||||
let origin_weight = min_create_bond::<T>() * 2u32.into();
|
||||
|
||||
// setup the worst case list scenario.
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
@@ -237,9 +241,7 @@ frame_benchmarking::benchmarks! {
|
||||
}
|
||||
|
||||
bond_extra_transfer {
|
||||
let origin_weight = pallet_nomination_pools::MinCreateBond::<T>::get()
|
||||
.max(CurrencyOf::<T>::minimum_balance())
|
||||
* 2u32.into();
|
||||
let origin_weight = min_create_bond::<T>() * 2u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let extra = scenario.dest_weight.clone() - origin_weight;
|
||||
|
||||
@@ -254,9 +256,7 @@ frame_benchmarking::benchmarks! {
|
||||
}
|
||||
|
||||
bond_extra_reward {
|
||||
let origin_weight = pallet_nomination_pools::MinCreateBond::<T>::get()
|
||||
.max(CurrencyOf::<T>::minimum_balance())
|
||||
* 2u32.into();
|
||||
let origin_weight = min_create_bond::<T>() * 2u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let extra = (scenario.dest_weight.clone() - origin_weight).max(CurrencyOf::<T>::minimum_balance());
|
||||
|
||||
@@ -274,7 +274,7 @@ frame_benchmarking::benchmarks! {
|
||||
}
|
||||
|
||||
claim_payout {
|
||||
let origin_weight = pallet_nomination_pools::MinCreateBond::<T>::get().max(CurrencyOf::<T>::minimum_balance()) * 2u32.into();
|
||||
let origin_weight = min_create_bond::<T>() * 2u32.into();
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, origin_weight);
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
@@ -304,9 +304,7 @@ frame_benchmarking::benchmarks! {
|
||||
unbond {
|
||||
// The weight the nominator will start at. The value used here is expected to be
|
||||
// significantly higher than the first position in a list (e.g. the first bag threshold).
|
||||
let origin_weight = BalanceOf::<T>::try_from(952_994_955_240_703u128)
|
||||
.map_err(|_| "balance expected to be a u128")
|
||||
.unwrap();
|
||||
let origin_weight = min_create_bond::<T>() * 200u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, false)?;
|
||||
let amount = origin_weight - scenario.dest_weight.clone();
|
||||
|
||||
@@ -336,9 +334,7 @@ frame_benchmarking::benchmarks! {
|
||||
pool_withdraw_unbonded {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = MinCreateBond::<T>::get()
|
||||
.max(T::StakingInterface::minimum_bond())
|
||||
.max(CurrencyOf::<T>::minimum_balance());
|
||||
let min_create_bond = min_create_bond::<T>();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond);
|
||||
|
||||
// Add a new member
|
||||
@@ -380,9 +376,7 @@ frame_benchmarking::benchmarks! {
|
||||
withdraw_unbonded_update {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = MinCreateBond::<T>::get()
|
||||
.max(T::StakingInterface::minimum_bond())
|
||||
.max(CurrencyOf::<T>::minimum_balance());
|
||||
let min_create_bond = min_create_bond::<T>();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond);
|
||||
|
||||
// Add a new member
|
||||
@@ -427,10 +421,7 @@ frame_benchmarking::benchmarks! {
|
||||
withdraw_unbonded_kill {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = MinCreateBond::<T>::get()
|
||||
.max(T::StakingInterface::minimum_bond())
|
||||
.max(CurrencyOf::<T>::minimum_balance());
|
||||
|
||||
let min_create_bond = min_create_bond::<T>();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond);
|
||||
|
||||
// We set the pool to the destroying state so the depositor can leave
|
||||
@@ -494,9 +485,7 @@ frame_benchmarking::benchmarks! {
|
||||
}
|
||||
|
||||
create {
|
||||
let min_create_bond = MinCreateBond::<T>::get()
|
||||
.max(T::StakingInterface::minimum_bond())
|
||||
.max(CurrencyOf::<T>::minimum_balance());
|
||||
let min_create_bond = min_create_bond::<T>();
|
||||
let depositor: T::AccountId = account("depositor", USER_SEED, 0);
|
||||
|
||||
// Give the depositor some balance to bond
|
||||
@@ -542,9 +531,7 @@ frame_benchmarking::benchmarks! {
|
||||
let n in 1 .. T::MaxNominations::get();
|
||||
|
||||
// Create a pool
|
||||
let min_create_bond = MinCreateBond::<T>::get()
|
||||
.max(T::StakingInterface::minimum_bond())
|
||||
.max(CurrencyOf::<T>::minimum_balance());
|
||||
let min_create_bond = min_create_bond::<T>() * 2u32.into();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond);
|
||||
|
||||
// Create some accounts to nominate. For the sake of benchmarking they don't need to be
|
||||
@@ -581,9 +568,7 @@ frame_benchmarking::benchmarks! {
|
||||
|
||||
set_state {
|
||||
// Create a pool
|
||||
let min_create_bond = MinCreateBond::<T>::get()
|
||||
.max(T::StakingInterface::minimum_bond())
|
||||
.max(CurrencyOf::<T>::minimum_balance());
|
||||
let min_create_bond = min_create_bond::<T>();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond);
|
||||
BondedPools::<T>::mutate(&1, |maybe_pool| {
|
||||
// Force the pool into an invalid state
|
||||
@@ -601,10 +586,7 @@ frame_benchmarking::benchmarks! {
|
||||
let n in 1 .. <T as pallet_nomination_pools::Config>::MaxMetadataLen::get();
|
||||
|
||||
// Create a pool
|
||||
let min_create_bond = MinCreateBond::<T>::get()
|
||||
.max(T::StakingInterface::minimum_bond())
|
||||
.max(CurrencyOf::<T>::minimum_balance());
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond);
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond::<T>() * 2u32.into());
|
||||
|
||||
// Create metadata of the max possible size
|
||||
let metadata: Vec<u8> = (0..n).map(|_| 42).collect();
|
||||
@@ -633,7 +615,7 @@ frame_benchmarking::benchmarks! {
|
||||
|
||||
update_roles {
|
||||
let first_id = pallet_nomination_pools::LastPoolId::<T>::get() + 1;
|
||||
let (root, _) = create_pool_account::<T>(0, CurrencyOf::<T>::minimum_balance() * 2u32.into());
|
||||
let (root, _) = create_pool_account::<T>(0, min_create_bond::<T>() * 2u32.into());
|
||||
let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED);
|
||||
}:_(
|
||||
Origin::Signed(root.clone()),
|
||||
@@ -653,6 +635,24 @@ frame_benchmarking::benchmarks! {
|
||||
)
|
||||
}
|
||||
|
||||
chill {
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond::<T>() * 2u32.into());
|
||||
|
||||
// Nominate with the pool.
|
||||
let validators: Vec<_> = (0..T::MaxNominations::get())
|
||||
.map(|i| account("stash", USER_SEED, i))
|
||||
.collect();
|
||||
|
||||
assert_ok!(Pools::<T>::nominate(Origin::Signed(depositor.clone()).into(), 1, validators));
|
||||
assert!(T::StakingInterface::nominations(Pools::<T>::create_bonded_account(1)).is_some());
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(Origin::Signed(depositor.clone()), 1)
|
||||
verify {
|
||||
assert!(T::StakingInterface::nominations(Pools::<T>::create_bonded_account(1)).is_none());
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pallet,
|
||||
crate::mock::new_test_ext(),
|
||||
|
||||
@@ -447,21 +447,27 @@ impl<T: Config> PoolMember<T> {
|
||||
.fold(BalanceOf::<T>::zero(), |acc, (_, v)| acc.saturating_add(*v))
|
||||
}
|
||||
|
||||
/// Try and unbond `points` from self, with the given target unbonding era.
|
||||
/// Try and unbond `points_dissolved` from self, and in return mint `points_issued` into the
|
||||
/// corresponding `era`'s unlock schedule.
|
||||
///
|
||||
/// In the absence of slashing, these two points are always the same. In the presence of
|
||||
/// slashing, the value of points in different pools varies.
|
||||
///
|
||||
/// Returns `Ok(())` and updates `unbonding_eras` and `points` if success, `Err(_)` otherwise.
|
||||
fn try_unbond(
|
||||
&mut self,
|
||||
points: BalanceOf<T>,
|
||||
points_dissolved: BalanceOf<T>,
|
||||
points_issued: BalanceOf<T>,
|
||||
unbonding_era: EraIndex,
|
||||
) -> Result<(), Error<T>> {
|
||||
if let Some(new_points) = self.points.checked_sub(&points) {
|
||||
if let Some(new_points) = self.points.checked_sub(&points_dissolved) {
|
||||
match self.unbonding_eras.get_mut(&unbonding_era) {
|
||||
Some(already_unbonding_points) =>
|
||||
*already_unbonding_points = already_unbonding_points.saturating_add(points),
|
||||
*already_unbonding_points =
|
||||
already_unbonding_points.saturating_add(points_issued),
|
||||
None => self
|
||||
.unbonding_eras
|
||||
.try_insert(unbonding_era, points)
|
||||
.try_insert(unbonding_era, points_issued)
|
||||
.map(|old| {
|
||||
if old.is_some() {
|
||||
defensive!("value checked to not exist in the map; qed");
|
||||
@@ -721,9 +727,13 @@ impl<T: Config> BondedPool<T> {
|
||||
}
|
||||
|
||||
fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf<T>) -> bool {
|
||||
// NOTE: if we add `&& self.member_counter == 1`, then this becomes even more strict and
|
||||
// ensures that there are no unbonding members hanging around either.
|
||||
self.is_destroying() && self.points == alleged_depositor_points
|
||||
// we need to ensure that `self.member_counter == 1` as well, because the depositor's
|
||||
// initial `MinCreateBond` (or more) is what guarantees that the ledger of the pool does not
|
||||
// get killed in the staking system, and that it does not fall below `MinimumNominatorBond`,
|
||||
// which could prevent other non-depositor members from fully leaving. Thus, all members
|
||||
// must withdraw, then depositor can unbond, and finally withdraw after waiting another
|
||||
// cycle.
|
||||
self.is_destroying() && self.points == alleged_depositor_points && self.member_counter == 1
|
||||
}
|
||||
|
||||
/// Whether or not the pool is ok to be in `PoolSate::Open`. If this returns an `Err`, then the
|
||||
@@ -984,10 +994,14 @@ impl<T: Config> UnbondPool<T> {
|
||||
Pallet::<T>::point_to_balance(self.balance, self.points, points)
|
||||
}
|
||||
|
||||
/// Issue points and update the balance given `new_balance`.
|
||||
fn issue(&mut self, new_funds: BalanceOf<T>) {
|
||||
self.points = self.points.saturating_add(self.balance_to_point(new_funds));
|
||||
/// Issue the equivalent points of `new_funds` into self.
|
||||
///
|
||||
/// Returns the actual amounts of points issued.
|
||||
fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
|
||||
let new_points = self.balance_to_point(new_funds);
|
||||
self.points = self.points.saturating_add(new_points);
|
||||
self.balance = self.balance.saturating_add(new_funds);
|
||||
new_points
|
||||
}
|
||||
|
||||
/// Dissolve some points from the unbonding pool, reducing the balance of the pool
|
||||
@@ -1150,6 +1164,9 @@ pub mod pallet {
|
||||
///
|
||||
/// This is the amount that the depositor must put as their initial stake in the pool, as an
|
||||
/// indication of "skin in the game".
|
||||
///
|
||||
/// This is the value that will always exist in the staking ledger of the pool bonded account
|
||||
/// while all other accounts leave.
|
||||
#[pallet::storage]
|
||||
pub type MinCreateBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
|
||||
|
||||
@@ -1256,9 +1273,34 @@ pub mod pallet {
|
||||
/// A payout has been made to a member.
|
||||
PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf<T> },
|
||||
/// A member has unbonded from their pool.
|
||||
Unbonded { member: T::AccountId, pool_id: PoolId, amount: BalanceOf<T> },
|
||||
///
|
||||
/// - `balance` is the corresponding balance of the number of points that has been
|
||||
/// requested to be unbonded (the argument of the `unbond` transaction) from the bonded
|
||||
/// pool.
|
||||
/// - `points` is the number of points that are issued as a result of `balance` being
|
||||
/// dissolved into the corresponding unbonding pool.
|
||||
///
|
||||
/// In the absence of slashing, these values will match. In the presence of slashing, the
|
||||
/// number of points that are issued in the unbonding pool will be less than the amount
|
||||
/// requested to be unbonded.
|
||||
Unbonded {
|
||||
member: T::AccountId,
|
||||
pool_id: PoolId,
|
||||
balance: BalanceOf<T>,
|
||||
points: BalanceOf<T>,
|
||||
},
|
||||
/// A member has withdrawn from their pool.
|
||||
Withdrawn { member: T::AccountId, pool_id: PoolId, amount: BalanceOf<T> },
|
||||
///
|
||||
/// The given number of `points` have been dissolved in return of `balance`.
|
||||
///
|
||||
/// Similar to `Unbonded` event, in the absence of slashing, the ratio of point to balance
|
||||
/// will be 1.
|
||||
Withdrawn {
|
||||
member: T::AccountId,
|
||||
pool_id: PoolId,
|
||||
balance: BalanceOf<T>,
|
||||
points: BalanceOf<T>,
|
||||
},
|
||||
/// A pool has been destroyed.
|
||||
Destroyed { pool_id: PoolId },
|
||||
/// The state of a pool has changed
|
||||
@@ -1274,6 +1316,10 @@ pub mod pallet {
|
||||
state_toggler: Option<T::AccountId>,
|
||||
nominator: Option<T::AccountId>,
|
||||
},
|
||||
/// The active balance of pool `pool_id` has been slashed to `balance`.
|
||||
PoolSlashed { pool_id: PoolId, balance: BalanceOf<T> },
|
||||
/// The unbond pool at `era` of pool `pool_id` has been slashed to `balance`.
|
||||
UnbondingPoolSlashed { pool_id: PoolId, era: EraIndex, balance: BalanceOf<T> },
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
@@ -1290,10 +1336,6 @@ pub mod pallet {
|
||||
/// An account is already delegating in another pool. An account may only belong to one
|
||||
/// pool at a time.
|
||||
AccountBelongsToOtherPool,
|
||||
/// The pool has insufficient balance to bond as a nominator.
|
||||
InsufficientBond,
|
||||
/// The member is already unbonding in this era.
|
||||
AlreadyUnbonding,
|
||||
/// The member is fully unbonded (and thus cannot access the bonded and reward pool
|
||||
/// anymore to, for example, collect rewards).
|
||||
FullyUnbonding,
|
||||
@@ -1346,6 +1388,9 @@ pub mod pallet {
|
||||
RewardPoolNotFound,
|
||||
/// A sub pool does not exist.
|
||||
SubPoolsNotFound,
|
||||
/// The bonded account should only be killed by the staking system when the depositor is
|
||||
/// withdrawing
|
||||
BondedStashKilledPrematurely,
|
||||
}
|
||||
|
||||
impl<T> From<DefensiveError> for Error<T> {
|
||||
@@ -1477,7 +1522,7 @@ pub mod pallet {
|
||||
|
||||
/// Unbond up to `unbonding_points` of the `member_account`'s funds from the pool. It
|
||||
/// implicitly collects the rewards one last time, since not doing so would mean some
|
||||
/// rewards would go forfeited.
|
||||
/// rewards would be forfeited.
|
||||
///
|
||||
/// Under certain conditions, this call can be dispatched permissionlessly (i.e. by any
|
||||
/// account).
|
||||
@@ -1528,9 +1573,6 @@ pub mod pallet {
|
||||
let current_era = T::StakingInterface::current_era();
|
||||
let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era);
|
||||
|
||||
// Try and unbond in the member map.
|
||||
member.try_unbond(unbonding_points, unbond_era)?;
|
||||
|
||||
// Unbond in the actual underlying nominator.
|
||||
let unbonding_balance = bonded_pool.dissolve(unbonding_points);
|
||||
T::StakingInterface::unbond(bonded_pool.bonded_account(), unbonding_balance)?;
|
||||
@@ -1553,17 +1595,21 @@ pub mod pallet {
|
||||
})?;
|
||||
}
|
||||
|
||||
sub_pools
|
||||
let points_unbonded = sub_pools
|
||||
.with_era
|
||||
.get_mut(&unbond_era)
|
||||
// The above check ensures the pool exists.
|
||||
.defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?
|
||||
.issue(unbonding_balance);
|
||||
|
||||
// Try and unbond in the member map.
|
||||
member.try_unbond(unbonding_points, points_unbonded, unbond_era)?;
|
||||
|
||||
Self::deposit_event(Event::<T>::Unbonded {
|
||||
member: member_account.clone(),
|
||||
pool_id: member.pool_id,
|
||||
amount: unbonding_balance,
|
||||
points: points_unbonded,
|
||||
balance: unbonding_balance,
|
||||
});
|
||||
|
||||
// Now that we know everything has worked write the items to storage.
|
||||
@@ -1644,14 +1690,23 @@ pub mod pallet {
|
||||
|
||||
// Before calculate the `balance_to_unbond`, with call withdraw unbonded to ensure the
|
||||
// `transferrable_balance` is correct.
|
||||
T::StakingInterface::withdraw_unbonded(
|
||||
let stash_killed = T::StakingInterface::withdraw_unbonded(
|
||||
bonded_pool.bonded_account(),
|
||||
num_slashing_spans,
|
||||
)?;
|
||||
|
||||
// defensive-only: the depositor puts enough funds into the stash so that it will only
|
||||
// be destroyed when they are leaving.
|
||||
ensure!(
|
||||
!stash_killed || caller == bonded_pool.roles.depositor,
|
||||
Error::<T>::Defensive(DefensiveError::BondedStashKilledPrematurely)
|
||||
);
|
||||
|
||||
let mut sum_unlocked_points: BalanceOf<T> = Zero::zero();
|
||||
let balance_to_unbond = withdrawn_points
|
||||
.iter()
|
||||
.fold(BalanceOf::<T>::zero(), |accumulator, (era, unlocked_points)| {
|
||||
sum_unlocked_points = sum_unlocked_points.saturating_add(*unlocked_points);
|
||||
if let Some(era_pool) = sub_pools.with_era.get_mut(&era) {
|
||||
let balance_to_unbond = era_pool.dissolve(*unlocked_points);
|
||||
if era_pool.points.is_zero() {
|
||||
@@ -1684,7 +1739,8 @@ pub mod pallet {
|
||||
Self::deposit_event(Event::<T>::Withdrawn {
|
||||
member: member_account.clone(),
|
||||
pool_id: member.pool_id,
|
||||
amount: balance_to_unbond,
|
||||
points: sum_unlocked_points,
|
||||
balance: balance_to_unbond,
|
||||
});
|
||||
|
||||
let post_info_weight = if member.total_points().is_zero() {
|
||||
@@ -1811,6 +1867,13 @@ pub mod pallet {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Nominate on behalf of the pool.
|
||||
///
|
||||
/// The dispatch origin of this call must be signed by the pool nominator or the pool
|
||||
/// root role.
|
||||
///
|
||||
/// This directly forward the call to the staking pallet, on behalf of the pool bonded
|
||||
/// account.
|
||||
#[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))]
|
||||
pub fn nominate(
|
||||
origin: OriginFor<T>,
|
||||
@@ -1820,10 +1883,13 @@ pub mod pallet {
|
||||
let who = ensure_signed(origin)?;
|
||||
let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
|
||||
ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
|
||||
T::StakingInterface::nominate(bonded_pool.bonded_account(), validators)?;
|
||||
Ok(())
|
||||
T::StakingInterface::nominate(bonded_pool.bonded_account(), validators)
|
||||
}
|
||||
|
||||
/// Set a new state for the pool.
|
||||
///
|
||||
/// The dispatch origin of this call must be signed by the state toggler, or the root role
|
||||
/// of the pool.
|
||||
#[pallet::weight(T::WeightInfo::set_state())]
|
||||
pub fn set_state(
|
||||
origin: OriginFor<T>,
|
||||
@@ -1850,6 +1916,10 @@ pub mod pallet {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set a new metadata for the pool.
|
||||
///
|
||||
/// The dispatch origin of this call must be signed by the state toggler, or the root role
|
||||
/// of the pool.
|
||||
#[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))]
|
||||
pub fn set_metadata(
|
||||
origin: OriginFor<T>,
|
||||
@@ -1961,6 +2031,21 @@ pub mod pallet {
|
||||
bonded_pool.put();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Chill on behalf of the pool.
|
||||
///
|
||||
/// The dispatch origin of this call must be signed by the pool nominator or the pool
|
||||
/// root role, same as [`Pallet::nominate`].
|
||||
///
|
||||
/// This directly forward the call to the staking pallet, on behalf of the pool bonded
|
||||
/// account.
|
||||
#[pallet::weight(T::WeightInfo::chill())]
|
||||
pub fn chill(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
|
||||
ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
|
||||
T::StakingInterface::chill(bonded_pool.bonded_account())
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
@@ -2175,6 +2260,7 @@ impl<T: Config> Pallet<T> {
|
||||
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);
|
||||
@@ -2355,19 +2441,26 @@ impl<T: Config> OnStakerSlash<T::AccountId, BalanceOf<T>> for Pallet<T> {
|
||||
pool_account: &T::AccountId,
|
||||
// Bonded balance is always read directly from staking, therefore we need not update
|
||||
// anything here.
|
||||
_slashed_bonded: BalanceOf<T>,
|
||||
slashed_bonded: BalanceOf<T>,
|
||||
slashed_unlocking: &BTreeMap<EraIndex, BalanceOf<T>>,
|
||||
) {
|
||||
if let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account).defensive() {
|
||||
if let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) {
|
||||
let mut sub_pools = match SubPoolsStorage::<T>::get(pool_id).defensive() {
|
||||
Some(sub_pools) => sub_pools,
|
||||
None => return,
|
||||
};
|
||||
for (era, slashed_balance) in slashed_unlocking.iter() {
|
||||
if let Some(pool) = sub_pools.with_era.get_mut(era) {
|
||||
pool.balance = *slashed_balance
|
||||
pool.balance = *slashed_balance;
|
||||
Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
|
||||
era: *era,
|
||||
pool_id,
|
||||
balance: *slashed_balance,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
|
||||
SubPoolsStorage::<T>::insert(pool_id, sub_pools);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use super::*;
|
||||
use crate::{self as pools};
|
||||
use frame_support::{assert_ok, parameter_types, PalletId};
|
||||
use frame_system::RawOrigin;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub type AccountId = u128;
|
||||
pub type Balance = u128;
|
||||
@@ -20,17 +19,19 @@ pub fn default_reward_account() -> AccountId {
|
||||
parameter_types! {
|
||||
pub static CurrentEra: EraIndex = 0;
|
||||
pub static BondingDuration: EraIndex = 3;
|
||||
static BondedBalanceMap: HashMap<AccountId, Balance> = Default::default();
|
||||
static UnbondingBalanceMap: HashMap<AccountId, Balance> = Default::default();
|
||||
pub storage BondedBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
|
||||
pub storage UnbondingBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub static MaxUnbonding: u32 = 8;
|
||||
pub static Nominations: Vec<AccountId> = vec![];
|
||||
pub storage Nominations: Option<Vec<AccountId>> = None;
|
||||
}
|
||||
|
||||
pub struct StakingMock;
|
||||
impl StakingMock {
|
||||
pub(crate) fn set_bonded_balance(who: AccountId, bonded: Balance) {
|
||||
BONDED_BALANCE_MAP.with(|m| m.borrow_mut().insert(who, bonded));
|
||||
let mut x = BondedBalanceMap::get();
|
||||
x.insert(who, bonded);
|
||||
BondedBalanceMap::set(&x)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,22 +67,33 @@ impl sp_staking::StakingInterface for StakingMock {
|
||||
}
|
||||
|
||||
fn bond_extra(who: Self::AccountId, extra: Self::Balance) -> DispatchResult {
|
||||
BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(&who).unwrap() += extra);
|
||||
let mut x = BondedBalanceMap::get();
|
||||
x.get_mut(&who).map(|v| *v += extra);
|
||||
BondedBalanceMap::set(&x);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unbond(who: Self::AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(&who).unwrap() -= amount);
|
||||
UNBONDING_BALANCE_MAP
|
||||
.with(|m| *m.borrow_mut().entry(who).or_insert(Self::Balance::zero()) += amount);
|
||||
let mut x = BondedBalanceMap::get();
|
||||
*x.get_mut(&who).unwrap() = x.get_mut(&who).unwrap().saturating_sub(amount);
|
||||
BondedBalanceMap::set(&x);
|
||||
let mut y = UnbondingBalanceMap::get();
|
||||
*y.entry(who).or_insert(Self::Balance::zero()) += amount;
|
||||
UnbondingBalanceMap::set(&y);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result<u64, DispatchError> {
|
||||
// Simulates removing unlocking chunks and only having the bonded balance locked
|
||||
let _maybe_new_free = UNBONDING_BALANCE_MAP.with(|m| m.borrow_mut().remove(&who));
|
||||
fn chill(_: Self::AccountId) -> sp_runtime::DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok(100)
|
||||
fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result<bool, DispatchError> {
|
||||
// Simulates removing unlocking chunks and only having the bonded balance locked
|
||||
let mut x = UnbondingBalanceMap::get();
|
||||
x.remove(&who);
|
||||
UnbondingBalanceMap::set(&x);
|
||||
|
||||
Ok(UnbondingBalanceMap::get().is_empty() && BondedBalanceMap::get().is_empty())
|
||||
}
|
||||
|
||||
fn bond(
|
||||
@@ -95,9 +107,13 @@ impl sp_staking::StakingInterface for StakingMock {
|
||||
}
|
||||
|
||||
fn nominate(_: Self::AccountId, nominations: Vec<Self::AccountId>) -> DispatchResult {
|
||||
Nominations::set(nominations);
|
||||
Nominations::set(&Some(nominations));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn nominations(_: Self::AccountId) -> Option<Vec<Self::AccountId>> {
|
||||
Nominations::get()
|
||||
}
|
||||
}
|
||||
|
||||
impl frame_system::Config for Runtime {
|
||||
@@ -162,7 +178,6 @@ parameter_types! {
|
||||
pub static MaxMetadataLen: u32 = 2;
|
||||
pub static CheckLevel: u8 = 255;
|
||||
pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
|
||||
pub const MinPointsToBalance: u32 = 10;
|
||||
}
|
||||
impl pools::Config for Runtime {
|
||||
type Event = Event;
|
||||
@@ -175,7 +190,7 @@ impl pools::Config for Runtime {
|
||||
type PalletId = PoolsPalletId;
|
||||
type MaxMetadataLen = MaxMetadataLen;
|
||||
type MaxUnbonding = MaxUnbonding;
|
||||
type MinPointsToBalance = MinPointsToBalance;
|
||||
type MinPointsToBalance = frame_support::traits::ConstU32<10>;
|
||||
}
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Runtime>;
|
||||
@@ -265,7 +280,8 @@ pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) -> Result<(),
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
static ObservedEvents: usize = 0;
|
||||
static PoolsEvents: usize = 0;
|
||||
static BalancesEvents: usize = 0;
|
||||
}
|
||||
|
||||
/// All events of this pallet.
|
||||
@@ -275,8 +291,20 @@ pub(crate) fn pool_events_since_last_call() -> Vec<super::Event<Runtime>> {
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let Event::Pools(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = ObservedEvents::get();
|
||||
ObservedEvents::set(events.len());
|
||||
let already_seen = PoolsEvents::get();
|
||||
PoolsEvents::set(events.len());
|
||||
events.into_iter().skip(already_seen).collect()
|
||||
}
|
||||
|
||||
/// All events of the `Balances` pallet.
|
||||
pub(crate) fn balances_events_since_last_call() -> Vec<pallet_balances::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.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()
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,11 +18,12 @@
|
||||
//! Autogenerated weights for pallet_nomination_pools
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! DATE: 2022-06-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// ./target/production/substrate
|
||||
// target/production/substrate
|
||||
// benchmark
|
||||
// pallet
|
||||
// --chain=dev
|
||||
@@ -32,8 +33,9 @@
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=compiled
|
||||
// --template=./.maintain/frame-weight-template.hbs
|
||||
// --heap-pages=4096
|
||||
// --output=./frame/nomination-pools/src/weights.rs
|
||||
// --template=./.maintain/frame-weight-template.hbs
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
@@ -58,6 +60,7 @@ pub trait WeightInfo {
|
||||
fn set_metadata(n: u32, ) -> Weight;
|
||||
fn set_configs() -> Weight;
|
||||
fn update_roles() -> Weight;
|
||||
fn chill() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for pallet_nomination_pools using the Substrate node and recommended hardware.
|
||||
@@ -77,7 +80,7 @@ impl<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 {
|
||||
(129_124_000 as Weight)
|
||||
(124_508_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(17 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(11 as Weight))
|
||||
}
|
||||
@@ -91,7 +94,7 @@ impl<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_transfer() -> Weight {
|
||||
(118_193_000 as Weight)
|
||||
(115_185_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(13 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(12 as Weight))
|
||||
}
|
||||
@@ -102,19 +105,19 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
// Storage: Staking Ledger (r:1 w:1)
|
||||
// Storage: Staking Bonded (r:1 w:0)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
// Storage: BagsList ListNodes (r:2 w:2)
|
||||
// Storage: BagsList ListNodes (r:3 w:3)
|
||||
// Storage: BagsList ListBags (r:2 w:2)
|
||||
fn bond_extra_reward() -> Weight {
|
||||
(132_390_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(13 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(12 as Weight))
|
||||
(132_723_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(14 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(13 as Weight))
|
||||
}
|
||||
// Storage: NominationPools PoolMembers (r:1 w:1)
|
||||
// Storage: NominationPools BondedPools (r:1 w:1)
|
||||
// Storage: NominationPools RewardPools (r:1 w:1)
|
||||
// Storage: System Account (r:1 w:1)
|
||||
fn claim_payout() -> Weight {
|
||||
(54_743_000 as Weight)
|
||||
(52_498_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
@@ -133,7 +136,7 @@ impl<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 {
|
||||
(124_684_000 as Weight)
|
||||
(121_645_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(18 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(13 as Weight))
|
||||
}
|
||||
@@ -141,10 +144,11 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
// Storage: Staking Ledger (r:1 w:1)
|
||||
// Storage: Staking CurrentEra (r:1 w:0)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
/// The range of component `s` is `[0, 100]`.
|
||||
fn pool_withdraw_unbonded(s: u32, ) -> Weight {
|
||||
(44_259_000 as Weight)
|
||||
(43_320_000 as Weight)
|
||||
// Standard Error: 0
|
||||
.saturating_add((51_000 as Weight).saturating_mul(s as Weight))
|
||||
.saturating_add((49_000 as Weight).saturating_mul(s as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
@@ -156,10 +160,11 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
// Storage: System Account (r:1 w:1)
|
||||
// Storage: NominationPools CounterForPoolMembers (r:1 w:1)
|
||||
/// The range of component `s` is `[0, 100]`.
|
||||
fn withdraw_unbonded_update(s: u32, ) -> Weight {
|
||||
(84_854_000 as Weight)
|
||||
// Standard Error: 0
|
||||
.saturating_add((56_000 as Weight).saturating_mul(s as Weight))
|
||||
(83_195_000 as Weight)
|
||||
// Standard Error: 5_000
|
||||
.saturating_add((57_000 as Weight).saturating_mul(s as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(8 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(7 as Weight))
|
||||
}
|
||||
@@ -182,8 +187,9 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
// Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1)
|
||||
// Storage: NominationPools CounterForBondedPools (r:1 w:1)
|
||||
// Storage: Staking Payee (r:0 w:1)
|
||||
/// The range of component `s` is `[0, 100]`.
|
||||
fn withdraw_unbonded_kill(_s: u32, ) -> Weight {
|
||||
(146_992_000 as Weight)
|
||||
(143_495_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(19 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(16 as Weight))
|
||||
}
|
||||
@@ -210,7 +216,7 @@ impl<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 {
|
||||
(138_099_000 as Weight)
|
||||
(127_998_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(22 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(15 as Weight))
|
||||
}
|
||||
@@ -226,10 +232,11 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
// Storage: BagsList ListBags (r:1 w:1)
|
||||
// Storage: BagsList CounterForListNodes (r:1 w:1)
|
||||
// Storage: Staking CounterForNominators (r:1 w:1)
|
||||
/// The range of component `n` is `[1, 16]`.
|
||||
fn nominate(n: u32, ) -> Weight {
|
||||
(50_964_000 as Weight)
|
||||
// Standard Error: 11_000
|
||||
.saturating_add((2_333_000 as Weight).saturating_mul(n as Weight))
|
||||
(49_929_000 as Weight)
|
||||
// Standard Error: 16_000
|
||||
.saturating_add((2_319_000 as Weight).saturating_mul(n as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(12 as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight)))
|
||||
.saturating_add(T::DbWeight::get().writes(5 as Weight))
|
||||
@@ -237,15 +244,16 @@ impl<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_196_000 as Weight)
|
||||
(27_399_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: NominationPools BondedPools (r:1 w:0)
|
||||
// Storage: NominationPools Metadata (r:1 w:1)
|
||||
// Storage: NominationPools CounterForMetadata (r:1 w:1)
|
||||
/// The range of component `n` is `[1, 256]`.
|
||||
fn set_metadata(n: u32, ) -> Weight {
|
||||
(15_056_000 as Weight)
|
||||
(14_813_000 as Weight)
|
||||
// Standard Error: 0
|
||||
.saturating_add((1_000 as Weight).saturating_mul(n as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
@@ -257,15 +265,28 @@ impl<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_294_000 as Weight)
|
||||
(6_115_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().writes(5 as Weight))
|
||||
}
|
||||
// Storage: NominationPools BondedPools (r:1 w:1)
|
||||
fn update_roles() -> Weight {
|
||||
(22_444_000 as Weight)
|
||||
(22_546_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(1 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: NominationPools BondedPools (r:1 w:0)
|
||||
// Storage: Staking Ledger (r:1 w:0)
|
||||
// Storage: Staking Validators (r:1 w:0)
|
||||
// Storage: Staking Nominators (r:1 w:1)
|
||||
// Storage: Staking CounterForNominators (r:1 w:1)
|
||||
// Storage: BagsList ListNodes (r:1 w:1)
|
||||
// Storage: BagsList ListBags (r:1 w:1)
|
||||
// Storage: BagsList CounterForListNodes (r:1 w:1)
|
||||
fn chill() -> Weight {
|
||||
(48_243_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(8 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(5 as Weight))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
@@ -284,7 +305,7 @@ impl WeightInfo for () {
|
||||
// Storage: BagsList ListNodes (r:3 w:3)
|
||||
// Storage: BagsList ListBags (r:2 w:2)
|
||||
fn join() -> Weight {
|
||||
(129_124_000 as Weight)
|
||||
(124_508_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(17 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(11 as Weight))
|
||||
}
|
||||
@@ -298,7 +319,7 @@ impl WeightInfo for () {
|
||||
// Storage: BagsList ListNodes (r:3 w:3)
|
||||
// Storage: BagsList ListBags (r:2 w:2)
|
||||
fn bond_extra_transfer() -> Weight {
|
||||
(118_193_000 as Weight)
|
||||
(115_185_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(13 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(12 as Weight))
|
||||
}
|
||||
@@ -309,19 +330,19 @@ impl WeightInfo for () {
|
||||
// Storage: Staking Ledger (r:1 w:1)
|
||||
// Storage: Staking Bonded (r:1 w:0)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
// Storage: BagsList ListNodes (r:2 w:2)
|
||||
// Storage: BagsList ListNodes (r:3 w:3)
|
||||
// Storage: BagsList ListBags (r:2 w:2)
|
||||
fn bond_extra_reward() -> Weight {
|
||||
(132_390_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(13 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(12 as Weight))
|
||||
(132_723_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(14 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(13 as Weight))
|
||||
}
|
||||
// Storage: NominationPools PoolMembers (r:1 w:1)
|
||||
// Storage: NominationPools BondedPools (r:1 w:1)
|
||||
// Storage: NominationPools RewardPools (r:1 w:1)
|
||||
// Storage: System Account (r:1 w:1)
|
||||
fn claim_payout() -> Weight {
|
||||
(54_743_000 as Weight)
|
||||
(52_498_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
@@ -340,7 +361,7 @@ impl WeightInfo for () {
|
||||
// Storage: NominationPools SubPoolsStorage (r:1 w:1)
|
||||
// Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1)
|
||||
fn unbond() -> Weight {
|
||||
(124_684_000 as Weight)
|
||||
(121_645_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(18 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(13 as Weight))
|
||||
}
|
||||
@@ -348,10 +369,11 @@ impl WeightInfo for () {
|
||||
// Storage: Staking Ledger (r:1 w:1)
|
||||
// Storage: Staking CurrentEra (r:1 w:0)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
/// The range of component `s` is `[0, 100]`.
|
||||
fn pool_withdraw_unbonded(s: u32, ) -> Weight {
|
||||
(44_259_000 as Weight)
|
||||
(43_320_000 as Weight)
|
||||
// Standard Error: 0
|
||||
.saturating_add((51_000 as Weight).saturating_mul(s as Weight))
|
||||
.saturating_add((49_000 as Weight).saturating_mul(s as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
|
||||
}
|
||||
@@ -363,10 +385,11 @@ impl WeightInfo for () {
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
// Storage: System Account (r:1 w:1)
|
||||
// Storage: NominationPools CounterForPoolMembers (r:1 w:1)
|
||||
/// The range of component `s` is `[0, 100]`.
|
||||
fn withdraw_unbonded_update(s: u32, ) -> Weight {
|
||||
(84_854_000 as Weight)
|
||||
// Standard Error: 0
|
||||
.saturating_add((56_000 as Weight).saturating_mul(s as Weight))
|
||||
(83_195_000 as Weight)
|
||||
// Standard Error: 5_000
|
||||
.saturating_add((57_000 as Weight).saturating_mul(s as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(8 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(7 as Weight))
|
||||
}
|
||||
@@ -389,8 +412,9 @@ impl WeightInfo for () {
|
||||
// Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1)
|
||||
// Storage: NominationPools CounterForBondedPools (r:1 w:1)
|
||||
// Storage: Staking Payee (r:0 w:1)
|
||||
/// The range of component `s` is `[0, 100]`.
|
||||
fn withdraw_unbonded_kill(_s: u32, ) -> Weight {
|
||||
(146_992_000 as Weight)
|
||||
(143_495_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(19 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(16 as Weight))
|
||||
}
|
||||
@@ -417,7 +441,7 @@ impl WeightInfo for () {
|
||||
// Storage: NominationPools BondedPools (r:1 w:1)
|
||||
// Storage: Staking Payee (r:0 w:1)
|
||||
fn create() -> Weight {
|
||||
(138_099_000 as Weight)
|
||||
(127_998_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(22 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(15 as Weight))
|
||||
}
|
||||
@@ -433,10 +457,11 @@ impl WeightInfo for () {
|
||||
// Storage: BagsList ListBags (r:1 w:1)
|
||||
// Storage: BagsList CounterForListNodes (r:1 w:1)
|
||||
// Storage: Staking CounterForNominators (r:1 w:1)
|
||||
/// The range of component `n` is `[1, 16]`.
|
||||
fn nominate(n: u32, ) -> Weight {
|
||||
(50_964_000 as Weight)
|
||||
// Standard Error: 11_000
|
||||
.saturating_add((2_333_000 as Weight).saturating_mul(n as Weight))
|
||||
(49_929_000 as Weight)
|
||||
// Standard Error: 16_000
|
||||
.saturating_add((2_319_000 as Weight).saturating_mul(n as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(12 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight)))
|
||||
.saturating_add(RocksDbWeight::get().writes(5 as Weight))
|
||||
@@ -444,15 +469,16 @@ impl WeightInfo for () {
|
||||
// Storage: NominationPools BondedPools (r:1 w:1)
|
||||
// Storage: Staking Ledger (r:1 w:0)
|
||||
fn set_state() -> Weight {
|
||||
(27_196_000 as Weight)
|
||||
(27_399_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: NominationPools BondedPools (r:1 w:0)
|
||||
// Storage: NominationPools Metadata (r:1 w:1)
|
||||
// Storage: NominationPools CounterForMetadata (r:1 w:1)
|
||||
/// The range of component `n` is `[1, 256]`.
|
||||
fn set_metadata(n: u32, ) -> Weight {
|
||||
(15_056_000 as Weight)
|
||||
(14_813_000 as Weight)
|
||||
// Standard Error: 0
|
||||
.saturating_add((1_000 as Weight).saturating_mul(n as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
@@ -464,13 +490,26 @@ impl WeightInfo for () {
|
||||
// Storage: NominationPools MinCreateBond (r:0 w:1)
|
||||
// Storage: NominationPools MaxPools (r:0 w:1)
|
||||
fn set_configs() -> Weight {
|
||||
(6_294_000 as Weight)
|
||||
(6_115_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().writes(5 as Weight))
|
||||
}
|
||||
// Storage: NominationPools BondedPools (r:1 w:1)
|
||||
fn update_roles() -> Weight {
|
||||
(22_444_000 as Weight)
|
||||
(22_546_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: NominationPools BondedPools (r:1 w:0)
|
||||
// Storage: Staking Ledger (r:1 w:0)
|
||||
// Storage: Staking Validators (r:1 w:0)
|
||||
// Storage: Staking Nominators (r:1 w:1)
|
||||
// Storage: Staking CounterForNominators (r:1 w:1)
|
||||
// Storage: BagsList ListNodes (r:1 w:1)
|
||||
// Storage: BagsList ListBags (r:1 w:1)
|
||||
// Storage: BagsList CounterForListNodes (r:1 w:1)
|
||||
fn chill() -> Weight {
|
||||
(48_243_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(8 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(5 as Weight))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
[package]
|
||||
name = "pallet-nomination-pools-test-staking"
|
||||
version = "1.0.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.io"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
description = "FRAME nomination pools pallet tests with the staking pallet"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dev-dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] }
|
||||
scale-info = { version = "2.0.1", features = ["derive"] }
|
||||
|
||||
sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" }
|
||||
sp-io = { version = "6.0.0", path = "../../../primitives/io" }
|
||||
sp-std = { version = "4.0.0", path = "../../../primitives/std" }
|
||||
sp-staking = { version = "4.0.0-dev", path = "../../../primitives/staking" }
|
||||
sp-core = { version = "6.0.0", path = "../../../primitives/core" }
|
||||
|
||||
frame-system = { version = "4.0.0-dev", path = "../../system" }
|
||||
frame-support = { version = "4.0.0-dev", path = "../../support" }
|
||||
frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support" }
|
||||
|
||||
pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" }
|
||||
pallet-balances = { version = "4.0.0-dev", path = "../../balances" }
|
||||
pallet-staking = { version = "4.0.0-dev", path = "../../staking" }
|
||||
pallet-bags-list = { version = "4.0.0-dev", path = "../../bags-list" }
|
||||
pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" }
|
||||
pallet-nomination-pools = { version = "1.0.0-dev", path = ".." }
|
||||
|
||||
sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" }
|
||||
log = { version = "0.4.0" }
|
||||
@@ -0,0 +1,373 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
mod mock;
|
||||
|
||||
use frame_support::{assert_noop, assert_ok, bounded_btree_map, traits::Currency};
|
||||
use mock::*;
|
||||
use pallet_nomination_pools::{
|
||||
Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember, PoolMembers, PoolState,
|
||||
};
|
||||
use pallet_staking::{CurrentEra, Event as StakingEvent, Payee, RewardDestination};
|
||||
|
||||
#[test]
|
||||
fn pool_lifecycle_e2e() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(Balances::minimum_balance(), 5);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(Origin::signed(10), 50, 10, 10, 10));
|
||||
assert_eq!(LastPoolId::<Runtime>::get(), 1);
|
||||
|
||||
// have the pool nominate.
|
||||
assert_ok!(Pools::nominate(Origin::signed(10), 1, vec![1, 2, 3]));
|
||||
|
||||
assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Bonded(POOL1_BONDED, 50),]);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// have two members join
|
||||
assert_ok!(Pools::join(Origin::signed(20), 10, 1));
|
||||
assert_ok!(Pools::join(Origin::signed(21), 10, 1));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded(POOL1_BONDED, 10), StakingEvent::Bonded(POOL1_BONDED, 10),]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
|
||||
PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// pool goes into destroying
|
||||
assert_ok!(Pools::set_state(Origin::signed(10), 1, PoolState::Destroying));
|
||||
|
||||
// depositor cannot unbond yet.
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(10), 10, 50),
|
||||
PoolsError::<Runtime>::NotOnlyPoolMember,
|
||||
);
|
||||
|
||||
// now the members want to unbond.
|
||||
assert_ok!(Pools::unbond(Origin::signed(20), 20, 10));
|
||||
assert_ok!(Pools::unbond(Origin::signed(21), 21, 10));
|
||||
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras.len(), 1);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 0);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().unbonding_eras.len(), 1);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().points, 0);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Unbonded(POOL1_BONDED, 10),
|
||||
StakingEvent::Unbonded(POOL1_BONDED, 10),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
|
||||
PoolsEvent::Unbonded { member: 20, pool_id: 1, points: 10, balance: 10 },
|
||||
PoolsEvent::Unbonded { member: 21, pool_id: 1, points: 10, balance: 10 },
|
||||
]
|
||||
);
|
||||
|
||||
// depositor cannot still unbond
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(10), 10, 50),
|
||||
PoolsError::<Runtime>::NotOnlyPoolMember,
|
||||
);
|
||||
|
||||
for e in 1..BondingDuration::get() {
|
||||
CurrentEra::<Runtime>::set(Some(e));
|
||||
assert_noop!(
|
||||
Pools::withdraw_unbonded(Origin::signed(20), 20, 0),
|
||||
PoolsError::<Runtime>::CannotWithdrawAny
|
||||
);
|
||||
}
|
||||
|
||||
// members are now unlocked.
|
||||
CurrentEra::<Runtime>::set(Some(BondingDuration::get()));
|
||||
|
||||
// depositor cannot still unbond
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(10), 10, 50),
|
||||
PoolsError::<Runtime>::NotOnlyPoolMember,
|
||||
);
|
||||
|
||||
// but members can now withdraw.
|
||||
assert_ok!(Pools::withdraw_unbonded(Origin::signed(20), 20, 0));
|
||||
assert_ok!(Pools::withdraw_unbonded(Origin::signed(21), 21, 0));
|
||||
assert!(PoolMembers::<Runtime>::get(20).is_none());
|
||||
assert!(PoolMembers::<Runtime>::get(21).is_none());
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Withdrawn(POOL1_BONDED, 20),]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Withdrawn { member: 20, pool_id: 1, points: 10, balance: 10 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 20 },
|
||||
PoolsEvent::Withdrawn { member: 21, pool_id: 1, points: 10, balance: 10 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 21 },
|
||||
]
|
||||
);
|
||||
|
||||
// as soon as all members have left, the depositor can try to unbond, but since the
|
||||
// min-nominator intention is set, they must chill first.
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(10), 10, 50),
|
||||
pallet_staking::Error::<Runtime>::InsufficientBond
|
||||
);
|
||||
|
||||
assert_ok!(Pools::chill(Origin::signed(10), 1));
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 50));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Chilled(POOL1_BONDED), StakingEvent::Unbonded(POOL1_BONDED, 50),]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50 }]
|
||||
);
|
||||
|
||||
// waiting another bonding duration:
|
||||
CurrentEra::<Runtime>::set(Some(BondingDuration::get() * 2));
|
||||
assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 1));
|
||||
|
||||
// pools is fully destroyed now.
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Withdrawn(POOL1_BONDED, 50),]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 10 },
|
||||
PoolsEvent::Destroyed { pool_id: 1 }
|
||||
]
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_slash_e2e() {
|
||||
new_test_ext().execute_with(|| {
|
||||
ExistentialDeposit::set(1);
|
||||
assert_eq!(Balances::minimum_balance(), 1);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(Origin::signed(10), 40, 10, 10, 10));
|
||||
assert_eq!(LastPoolId::<Runtime>::get(), 1);
|
||||
|
||||
assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Bonded(POOL1_BONDED, 40)]);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(Payee::<Runtime>::get(POOL1_BONDED), RewardDestination::Account(POOL1_REWARD));
|
||||
|
||||
// have two members join
|
||||
assert_ok!(Pools::join(Origin::signed(20), 20, 1));
|
||||
assert_ok!(Pools::join(Origin::signed(21), 20, 1));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded(POOL1_BONDED, 20), StakingEvent::Bonded(POOL1_BONDED, 20)]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
|
||||
PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 20, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// now let's progress a bit.
|
||||
CurrentEra::<Runtime>::set(Some(1));
|
||||
|
||||
// 20 / 80 of the total funds are unlocked, and safe from any further slash.
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 10));
|
||||
assert_ok!(Pools::unbond(Origin::signed(20), 20, 10));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Unbonded(POOL1_BONDED, 10),
|
||||
StakingEvent::Unbonded(POOL1_BONDED, 10)
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10 },
|
||||
PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10 }
|
||||
]
|
||||
);
|
||||
|
||||
CurrentEra::<Runtime>::set(Some(2));
|
||||
|
||||
// note: depositor cannot fully unbond at this point.
|
||||
// these funds will still get slashed.
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 10));
|
||||
assert_ok!(Pools::unbond(Origin::signed(20), 20, 10));
|
||||
assert_ok!(Pools::unbond(Origin::signed(21), 21, 10));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Unbonded(POOL1_BONDED, 10),
|
||||
StakingEvent::Unbonded(POOL1_BONDED, 10),
|
||||
StakingEvent::Unbonded(POOL1_BONDED, 10),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10 },
|
||||
PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10 },
|
||||
PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10 },
|
||||
]
|
||||
);
|
||||
|
||||
// At this point, 20 are safe from slash, 30 are unlocking but vulnerable to slash, and and
|
||||
// another 30 are active and vulnerable to slash. Let's slash half of them.
|
||||
pallet_staking::slashing::do_slash::<Runtime>(
|
||||
&POOL1_BONDED,
|
||||
30,
|
||||
&mut Default::default(),
|
||||
&mut Default::default(),
|
||||
1, // slash era 1, affects chunks at era 5 onwards.
|
||||
);
|
||||
|
||||
assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Slashed(POOL1_BONDED, 30)]);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
// 30 has been slashed to 15 (15 slash)
|
||||
PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 5, balance: 15 },
|
||||
// 30 has been slashed to 15 (15 slash)
|
||||
PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 }
|
||||
]
|
||||
);
|
||||
|
||||
CurrentEra::<Runtime>::set(Some(3));
|
||||
assert_ok!(Pools::unbond(Origin::signed(21), 21, 10));
|
||||
|
||||
assert_eq!(
|
||||
PoolMembers::<Runtime>::get(21).unwrap(),
|
||||
PoolMember {
|
||||
pool_id: 1,
|
||||
points: 0,
|
||||
reward_pool_total_earnings: 0,
|
||||
// the 10 points unlocked just now correspond to 5 points in the unbond pool.
|
||||
unbonding_eras: bounded_btree_map!(5 => 10, 6 => 5)
|
||||
}
|
||||
);
|
||||
assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Unbonded(POOL1_BONDED, 5)]);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 5, points: 5 }]
|
||||
);
|
||||
|
||||
// now we start withdrawing. we do it all at once, at era 6 where 20 and 21 are fully free.
|
||||
CurrentEra::<Runtime>::set(Some(6));
|
||||
assert_ok!(Pools::withdraw_unbonded(Origin::signed(20), 20, 0));
|
||||
assert_ok!(Pools::withdraw_unbonded(Origin::signed(21), 21, 0));
|
||||
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
// 20 had unbonded 10 safely, and 10 got slashed by half.
|
||||
PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 10 + 5, points: 20 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 20 },
|
||||
// 21 unbonded all of it after the slash
|
||||
PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 5 + 5, points: 15 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 21 }
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
// a 10 (un-slashed) + 10/2 (slashed) balance from 10 has also been unlocked
|
||||
vec![StakingEvent::Withdrawn(POOL1_BONDED, 15 + 10 + 15)]
|
||||
);
|
||||
|
||||
// now, finally, we can unbond the depositor further than their current limit.
|
||||
assert_ok!(Pools::set_state(Origin::signed(10), 1, PoolState::Destroying));
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 20));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded(POOL1_BONDED, 10)]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
|
||||
PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10 }
|
||||
]
|
||||
);
|
||||
|
||||
CurrentEra::<Runtime>::set(Some(9));
|
||||
assert_eq!(
|
||||
PoolMembers::<Runtime>::get(10).unwrap(),
|
||||
PoolMember {
|
||||
pool_id: 1,
|
||||
points: 0,
|
||||
reward_pool_total_earnings: 0,
|
||||
unbonding_eras: bounded_btree_map!(4 => 10, 5 => 10, 9 => 10)
|
||||
}
|
||||
);
|
||||
// withdraw the depositor, they should lose 12 balance in total due to slash.
|
||||
assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Withdrawn(POOL1_BONDED, 10)]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Withdrawn { member: 10, pool_id: 1, balance: 10 + 15, points: 30 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 10 },
|
||||
PoolsEvent::Destroyed { pool_id: 1 }
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use frame_election_provider_support::VoteWeight;
|
||||
use frame_support::{assert_ok, pallet_prelude::*, parameter_types, traits::ConstU64, PalletId};
|
||||
use sp_runtime::traits::{Convert, IdentityLookup};
|
||||
|
||||
type AccountId = u128;
|
||||
type AccountIndex = u32;
|
||||
type BlockNumber = u64;
|
||||
type Balance = u128;
|
||||
|
||||
pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128;
|
||||
pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128;
|
||||
|
||||
impl frame_system::Config for Runtime {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type Origin = Origin;
|
||||
type Index = AccountIndex;
|
||||
type BlockNumber = BlockNumber;
|
||||
type Call = Call;
|
||||
type Hash = sp_core::H256;
|
||||
type Hashing = sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = sp_runtime::testing::Header;
|
||||
type Event = Event;
|
||||
type BlockHashCount = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Runtime {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
type MinimumPeriod = ConstU64<5>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static ExistentialDeposit: Balance = 5;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for Runtime {
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type Balance = Balance;
|
||||
type Event = Event;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!(
|
||||
min_inflation: 0_025_000,
|
||||
max_inflation: 0_100_000,
|
||||
ideal_stake: 0_500_000,
|
||||
falloff: 0_050_000,
|
||||
max_piece_count: 40,
|
||||
test_precision: 0_005_000,
|
||||
);
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
||||
pub static BondingDuration: u32 = 3;
|
||||
}
|
||||
|
||||
impl pallet_staking::Config for Runtime {
|
||||
type MaxNominations = ConstU32<16>;
|
||||
type Currency = Balances;
|
||||
type CurrencyBalance = Balance;
|
||||
type UnixTime = pallet_timestamp::Pallet<Self>;
|
||||
type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote;
|
||||
type RewardRemainder = ();
|
||||
type Event = Event;
|
||||
type Slash = ();
|
||||
type Reward = ();
|
||||
type SessionsPerEra = ();
|
||||
type SlashDeferDuration = ();
|
||||
type SlashCancelOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||
type BondingDuration = BondingDuration;
|
||||
type SessionInterface = ();
|
||||
type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
|
||||
type NextNewSession = ();
|
||||
type MaxNominatorRewardedPerValidator = ConstU32<64>;
|
||||
type OffendingValidatorsThreshold = ();
|
||||
type ElectionProvider =
|
||||
frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type VoterList = pallet_bags_list::Pallet<Self>;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type OnStakerSlash = Pools;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000];
|
||||
}
|
||||
|
||||
impl pallet_bags_list::Config for Runtime {
|
||||
type Event = Event;
|
||||
type WeightInfo = ();
|
||||
type BagThresholds = BagThresholds;
|
||||
type ScoreProvider = Staking;
|
||||
type Score = VoteWeight;
|
||||
}
|
||||
|
||||
pub struct BalanceToU256;
|
||||
impl Convert<Balance, sp_core::U256> for BalanceToU256 {
|
||||
fn convert(n: Balance) -> sp_core::U256 {
|
||||
n.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct U256ToBalance;
|
||||
impl Convert<sp_core::U256, Balance> for U256ToBalance {
|
||||
fn convert(n: sp_core::U256) -> Balance {
|
||||
n.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const PostUnbondingPoolsWindow: u32 = 10;
|
||||
pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
|
||||
}
|
||||
|
||||
impl pallet_nomination_pools::Config for Runtime {
|
||||
type Event = Event;
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type BalanceToU256 = BalanceToU256;
|
||||
type U256ToBalance = U256ToBalance;
|
||||
type StakingInterface = Staking;
|
||||
type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
|
||||
type MaxMetadataLen = ConstU32<256>;
|
||||
type MaxUnbonding = ConstU32<8>;
|
||||
type MinPointsToBalance = ConstU32<10>;
|
||||
type PalletId = PoolsPalletId;
|
||||
}
|
||||
|
||||
impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Runtime
|
||||
where
|
||||
Call: From<LocalCall>,
|
||||
{
|
||||
type OverarchingCall = Call;
|
||||
type Extrinsic = UncheckedExtrinsic;
|
||||
}
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Runtime>;
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Runtime>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Runtime where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Event<T>},
|
||||
Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent},
|
||||
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
Staking: pallet_staking::{Pallet, Call, Config<T>, Storage, Event<T>},
|
||||
BagsList: pallet_bags_list::{Pallet, Call, Storage, Event<T>},
|
||||
Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event<T>},
|
||||
}
|
||||
);
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut storage = frame_system::GenesisConfig::default().build_storage::<Runtime>().unwrap();
|
||||
let _ = pallet_nomination_pools::GenesisConfig::<Runtime> {
|
||||
min_join_bond: 2,
|
||||
min_create_bond: 2,
|
||||
max_pools: Some(3),
|
||||
max_members_per_pool: Some(3),
|
||||
max_members: Some(3 * 3),
|
||||
}
|
||||
.assimilate_storage(&mut storage)
|
||||
.unwrap();
|
||||
|
||||
let _ = pallet_balances::GenesisConfig::<Runtime> {
|
||||
balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)],
|
||||
}
|
||||
.assimilate_storage(&mut storage)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::from(storage);
|
||||
|
||||
ext.execute_with(|| {
|
||||
// for events to be deposited.
|
||||
frame_system::Pallet::<Runtime>::set_block_number(1);
|
||||
|
||||
// set some limit for nominations.
|
||||
assert_ok!(Staking::set_staking_configs(
|
||||
Origin::root(),
|
||||
pallet_staking::ConfigOp::Set(10), // minimum nominator bond
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
));
|
||||
});
|
||||
|
||||
ext
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
static ObservedEventsPools: usize = 0;
|
||||
static ObservedEventsStaking: usize = 0;
|
||||
static ObservedEventsBalances: usize = 0;
|
||||
}
|
||||
|
||||
pub(crate) fn pool_events_since_last_call() -> Vec<pallet_nomination_pools::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let Event::Pools(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = ObservedEventsPools::get();
|
||||
ObservedEventsPools::set(events.len());
|
||||
events.into_iter().skip(already_seen).collect()
|
||||
}
|
||||
|
||||
pub(crate) fn staking_events_since_last_call() -> Vec<pallet_staking::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let Event::Staking(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = ObservedEventsStaking::get();
|
||||
ObservedEventsStaking::set(events.len());
|
||||
events.into_iter().skip(already_seen).collect()
|
||||
}
|
||||
@@ -438,7 +438,7 @@ pub struct UnlockChunk<Balance: HasCompact> {
|
||||
}
|
||||
|
||||
/// The ledger of a (bonded) stash.
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebugNoBound, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct StakingLedger<T: Config> {
|
||||
/// The stash account whose balance is actually locked and at stake.
|
||||
@@ -607,12 +607,12 @@ impl<T: Config> StakingLedger<T> {
|
||||
let mut slashed_unlocking = BTreeMap::<_, _>::new();
|
||||
for i in slash_chunks_priority {
|
||||
if let Some(chunk) = self.unlocking.get_mut(i).defensive() {
|
||||
slash_out_of(&mut chunk.value, &mut remaining_slash);
|
||||
// write the new slashed value of this chunk to the map.
|
||||
slashed_unlocking.insert(chunk.era, chunk.value);
|
||||
if remaining_slash.is_zero() {
|
||||
break
|
||||
}
|
||||
slash_out_of(&mut chunk.value, &mut remaining_slash);
|
||||
// write the new slashed value of this chunk to the map.
|
||||
slashed_unlocking.insert(chunk.era, chunk.value);
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1414,17 +1414,17 @@ impl<T: Config> StakingInterface for Pallet<T> {
|
||||
Self::unbond(RawOrigin::Signed(controller).into(), value)
|
||||
}
|
||||
|
||||
fn chill(controller: Self::AccountId) -> DispatchResult {
|
||||
Self::chill(RawOrigin::Signed(controller).into())
|
||||
}
|
||||
|
||||
fn withdraw_unbonded(
|
||||
controller: Self::AccountId,
|
||||
num_slashing_spans: u32,
|
||||
) -> Result<u64, DispatchError> {
|
||||
Self::withdraw_unbonded(RawOrigin::Signed(controller).into(), num_slashing_spans)
|
||||
.map(|post_info| {
|
||||
post_info
|
||||
.actual_weight
|
||||
.unwrap_or(T::WeightInfo::withdraw_unbonded_kill(num_slashing_spans))
|
||||
})
|
||||
.map_err(|err_with_post_info| err_with_post_info.error)
|
||||
) -> Result<bool, DispatchError> {
|
||||
Self::withdraw_unbonded(RawOrigin::Signed(controller.clone()).into(), num_slashing_spans)
|
||||
.map(|_| !Ledger::<T>::contains_key(&controller))
|
||||
.map_err(|with_post| with_post.error)
|
||||
}
|
||||
|
||||
fn bond(
|
||||
@@ -1445,4 +1445,9 @@ impl<T: Config> StakingInterface for Pallet<T> {
|
||||
let targets = targets.into_iter().map(T::Lookup::unlookup).collect::<Vec<_>>();
|
||||
Self::nominate(RawOrigin::Signed(controller).into(), targets)
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn nominations(who: Self::AccountId) -> Option<Vec<T::AccountId>> {
|
||||
Nominators::<T>::get(who).map(|n| n.targets.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4930,6 +4930,25 @@ fn force_apply_min_commission_works() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proportional_slash_stop_slashing_if_remaining_zero() {
|
||||
let c = |era, value| UnlockChunk::<Balance> { era, value };
|
||||
// Given
|
||||
let mut ledger = StakingLedger::<Test> {
|
||||
stash: 123,
|
||||
total: 40,
|
||||
active: 20,
|
||||
// we have some chunks, but they are not affected.
|
||||
unlocking: bounded_vec![c(1, 10), c(2, 10)],
|
||||
claimed_rewards: vec![],
|
||||
};
|
||||
|
||||
assert_eq!(BondingDuration::get(), 3);
|
||||
|
||||
// should not slash more than the amount requested, by accidentally slashing the first chunk.
|
||||
assert_eq!(ledger.slash(18, 1, 0), 18);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proportional_ledger_slash_works() {
|
||||
let c = |era, value| UnlockChunk::<Balance> { era, value };
|
||||
|
||||
@@ -1267,7 +1267,7 @@ pub trait StoragePrefixedMap<Value: FullCodec> {
|
||||
pub trait StorageAppend<Item: Encode>: private::Sealed {}
|
||||
|
||||
/// Marker trait that will be implemented for types that support to decode their length in an
|
||||
/// effificent way. It is expected that the length is at the beginning of the encoded object
|
||||
/// efficient way. It is expected that the length is at the beginning of the encoded object
|
||||
/// and that the length is a `Compact<u32>`.
|
||||
///
|
||||
/// This trait is sealed.
|
||||
|
||||
@@ -164,9 +164,9 @@ where
|
||||
|
||||
/// Execute the supplied function, adding a new storage layer.
|
||||
///
|
||||
/// This is the same as `with_transaction`, but assuming that any function returning
|
||||
/// an `Err` should rollback, and any function returning `Ok` should commit. This
|
||||
/// provides a cleaner API to the developer who wants this behavior.
|
||||
/// This is the same as `with_transaction`, but assuming that any function returning an `Err` should
|
||||
/// rollback, and any function returning `Ok` should commit. This provides a cleaner API to the
|
||||
/// developer who wants this behavior.
|
||||
pub fn with_storage_layer<T, E, F>(f: F) -> Result<T, E>
|
||||
where
|
||||
E: From<DispatchError>,
|
||||
|
||||
@@ -108,6 +108,9 @@ pub trait StakingInterface {
|
||||
validators: sp_std::vec::Vec<Self::AccountId>,
|
||||
) -> DispatchResult;
|
||||
|
||||
/// Chill `stash`.
|
||||
fn chill(controller: Self::AccountId) -> DispatchResult;
|
||||
|
||||
/// Bond some extra amount in the _Stash_'s free balance against the active bonded balance of
|
||||
/// the account. The amount extra actually bonded will never be more than the _Stash_'s free
|
||||
/// balance.
|
||||
@@ -125,8 +128,14 @@ pub trait StakingInterface {
|
||||
fn unbond(stash: Self::AccountId, value: Self::Balance) -> DispatchResult;
|
||||
|
||||
/// Unlock any funds schedule to unlock before or at the current era.
|
||||
///
|
||||
/// Returns whether the stash was killed because of this withdraw or not.
|
||||
fn withdraw_unbonded(
|
||||
stash: Self::AccountId,
|
||||
num_slashing_spans: u32,
|
||||
) -> Result<u64, DispatchError>;
|
||||
) -> Result<bool, DispatchError>;
|
||||
|
||||
/// Get the nominations of a stash, if they are a nominator, `None` otherwise.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn nominations(who: Self::AccountId) -> Option<sp_std::prelude::Vec<Self::AccountId>>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user