mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 20:01:08 +00:00
Fix nomination pools unbonding logic (#11746)
* 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 * new logic, some broken tests * Check if after unbonding remaining balance is more or equal to MinJoinBond and is not zero * incorporate nikos' work * make it work again * merge * Fix all tests * fix all tests * some updates * Add tests * remove erroneoysly placed comment * Try to make lint pass * Try to make lint pass * revamp the tests for unbond * fix docs * 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/nomination-pools/src/lib.rs Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> * remove log * 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 * fmrt * Update frame/nomination-pools/src/lib.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> 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> Co-authored-by: wirednkod <wirednkod@gmail.com>
This commit is contained in:
@@ -469,7 +469,7 @@ impl<T: Config> PoolMember<T> {
|
||||
self.points = new_points;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::<T>::NotEnoughPointsToUnbond)
|
||||
Err(Error::<T>::MinimumBondNotMet)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -781,45 +781,60 @@ impl<T: Config> BondedPool<T> {
|
||||
let is_depositor = *target_account == self.roles.depositor;
|
||||
let is_full_unbond = unbonding_points == target_member.active_points();
|
||||
|
||||
let balance_after_unbond = {
|
||||
let new_depositor_points =
|
||||
target_member.active_points().saturating_sub(unbonding_points);
|
||||
let mut target_member_after_unbond = (*target_member).clone();
|
||||
target_member_after_unbond.points = new_depositor_points;
|
||||
target_member_after_unbond.active_balance()
|
||||
};
|
||||
|
||||
// any partial unbonding is only ever allowed if this unbond is permissioned.
|
||||
ensure!(
|
||||
is_permissioned || is_full_unbond,
|
||||
Error::<T>::PartialUnbondNotAllowedPermissionlessly
|
||||
);
|
||||
|
||||
// any unbond must comply with the balance condition:
|
||||
ensure!(
|
||||
is_full_unbond ||
|
||||
balance_after_unbond >=
|
||||
if is_depositor {
|
||||
Pallet::<T>::depositor_min_bond()
|
||||
} else {
|
||||
MinJoinBond::<T>::get()
|
||||
},
|
||||
Error::<T>::MinimumBondNotMet
|
||||
);
|
||||
|
||||
// additional checks:
|
||||
match (is_permissioned, is_depositor) {
|
||||
// If the pool is blocked, then an admin with kicking permissions can remove a
|
||||
// member. If the pool is being destroyed, anyone can remove a member
|
||||
(true, false) => (),
|
||||
(true, true) => {
|
||||
// permission depositor unbond: if destroying and pool is empty, always allowed,
|
||||
// with no additional limits.
|
||||
if self.is_destroying_and_only_depositor(target_member.active_points()) {
|
||||
// everything good, let them unbond anything.
|
||||
} else {
|
||||
// depositor cannot fully unbond yet.
|
||||
ensure!(!is_full_unbond, Error::<T>::MinimumBondNotMet);
|
||||
}
|
||||
},
|
||||
(false, false) => {
|
||||
// If the pool is blocked, then an admin with kicking permissions can remove a
|
||||
// member. If the pool is being destroyed, anyone can remove a member
|
||||
debug_assert!(is_full_unbond);
|
||||
ensure!(
|
||||
self.can_kick(caller) || self.is_destroying(),
|
||||
Error::<T>::NotKickerOrDestroying
|
||||
)
|
||||
},
|
||||
// Any member who is not the depositor can always unbond themselves
|
||||
(true, false) => (),
|
||||
(_, true) => {
|
||||
if self.is_destroying_and_only_depositor(target_member.active_points()) {
|
||||
// if the pool is about to be destroyed, anyone can unbond the depositor, and
|
||||
// they can fully unbond.
|
||||
} else {
|
||||
// only the depositor can partially unbond, and they can only unbond up to the
|
||||
// threshold.
|
||||
ensure!(is_permissioned, Error::<T>::DoesNotHavePermission);
|
||||
let balance_after_unbond = {
|
||||
let new_depositor_points =
|
||||
target_member.active_points().saturating_sub(unbonding_points);
|
||||
let mut depositor_after_unbond = (*target_member).clone();
|
||||
depositor_after_unbond.points = new_depositor_points;
|
||||
depositor_after_unbond.active_balance()
|
||||
};
|
||||
ensure!(
|
||||
balance_after_unbond >= MinCreateBond::<T>::get(),
|
||||
Error::<T>::NotOnlyPoolMember
|
||||
);
|
||||
}
|
||||
(false, true) => {
|
||||
// the depositor can simply not be unbonded permissionlessly, period.
|
||||
return Err(Error::<T>::DoesNotHavePermission.into())
|
||||
},
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -830,25 +845,14 @@ impl<T: Config> BondedPool<T> {
|
||||
&self,
|
||||
caller: &T::AccountId,
|
||||
target_account: &T::AccountId,
|
||||
target_member: &PoolMember<T>,
|
||||
sub_pools: &SubPools<T>,
|
||||
) -> Result<(), DispatchError> {
|
||||
if *target_account == self.roles.depositor {
|
||||
ensure!(
|
||||
sub_pools.sum_unbonding_points() == target_member.unbonding_points(),
|
||||
Error::<T>::NotOnlyPoolMember
|
||||
);
|
||||
debug_assert_eq!(self.member_counter, 1, "only member must exist at this point");
|
||||
Ok(())
|
||||
} else {
|
||||
// This isn't a depositor
|
||||
let is_permissioned = caller == target_account;
|
||||
ensure!(
|
||||
is_permissioned || self.can_kick(caller) || self.is_destroying(),
|
||||
Error::<T>::NotKickerOrDestroying
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
// This isn't a depositor
|
||||
let is_permissioned = caller == target_account;
|
||||
ensure!(
|
||||
is_permissioned || self.can_kick(caller) || self.is_destroying(),
|
||||
Error::<T>::NotKickerOrDestroying
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Bond exactly `amount` from `who`'s funds into this pool.
|
||||
@@ -1100,15 +1104,6 @@ impl<T: Config> SubPools<T> {
|
||||
self
|
||||
}
|
||||
|
||||
/// The sum of all unbonding points, regardless of whether they are actually unlocked or not.
|
||||
fn sum_unbonding_points(&self) -> BalanceOf<T> {
|
||||
self.no_era.points.saturating_add(
|
||||
self.with_era
|
||||
.values()
|
||||
.fold(BalanceOf::<T>::zero(), |acc, pool| acc.saturating_add(pool.points)),
|
||||
)
|
||||
}
|
||||
|
||||
/// The sum of all unbonding balance, regardless of whether they are actually unlocked or not.
|
||||
#[cfg(any(test, debug_assertions))]
|
||||
fn sum_unbonding_balance(&self) -> BalanceOf<T> {
|
||||
@@ -1419,15 +1414,16 @@ pub mod pallet {
|
||||
/// None of the funds can be withdrawn yet because the bonding duration has not passed.
|
||||
CannotWithdrawAny,
|
||||
/// The amount does not meet the minimum bond to either join or create a pool.
|
||||
///
|
||||
/// The depositor can never unbond to a value less than
|
||||
/// `Pallet::depositor_min_bond`. The caller does not have nominating
|
||||
/// permissions for the pool. Members can never unbond to a value below `MinJoinBond`.
|
||||
MinimumBondNotMet,
|
||||
/// The transaction could not be executed due to overflow risk for the pool.
|
||||
OverflowRisk,
|
||||
/// A pool must be in [`PoolState::Destroying`] in order for the depositor to unbond or for
|
||||
/// other members to be permissionlessly unbonded.
|
||||
NotDestroying,
|
||||
/// The depositor must be the only member in the bonded pool in order to unbond. And the
|
||||
/// depositor must be the only member in the sub pools in order to withdraw unbonded.
|
||||
NotOnlyPoolMember,
|
||||
/// The caller does not have nominating permissions for the pool.
|
||||
NotNominator,
|
||||
/// Either a) the caller cannot make a valid kick or b) the pool is not destroying.
|
||||
@@ -1447,8 +1443,6 @@ pub mod pallet {
|
||||
/// Some error occurred that should never happen. This should be reported to the
|
||||
/// maintainers.
|
||||
Defensive(DefensiveError),
|
||||
/// Not enough points. Ty unbonding less.
|
||||
NotEnoughPointsToUnbond,
|
||||
/// Partial unbonding now allowed permissionlessly.
|
||||
PartialUnbondNotAllowedPermissionlessly,
|
||||
}
|
||||
@@ -1758,12 +1752,7 @@ pub mod pallet {
|
||||
let mut sub_pools = SubPoolsStorage::<T>::get(member.pool_id)
|
||||
.defensive_ok_or::<Error<T>>(DefensiveError::SubPoolsNotFound.into())?;
|
||||
|
||||
bonded_pool.ok_to_withdraw_unbonded_with(
|
||||
&caller,
|
||||
&member_account,
|
||||
&member,
|
||||
&sub_pools,
|
||||
)?;
|
||||
bonded_pool.ok_to_withdraw_unbonded_with(&caller, &member_account)?;
|
||||
|
||||
// NOTE: must do this after we have done the `ok_to_withdraw_unbonded_other_with` check.
|
||||
let withdrawn_points = member.withdraw_unlocked(current_era);
|
||||
@@ -1878,13 +1867,7 @@ pub mod pallet {
|
||||
) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
ensure!(
|
||||
amount >=
|
||||
T::StakingInterface::minimum_bond()
|
||||
.max(MinCreateBond::<T>::get())
|
||||
.max(MinJoinBond::<T>::get()),
|
||||
Error::<T>::MinimumBondNotMet
|
||||
);
|
||||
ensure!(amount >= Pallet::<T>::depositor_min_bond(), Error::<T>::MinimumBondNotMet);
|
||||
ensure!(
|
||||
MaxPools::<T>::get()
|
||||
.map_or(true, |max_pools| BondedPools::<T>::count() < max_pools),
|
||||
@@ -2162,6 +2145,18 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// The amount of bond that MUST REMAIN IN BONDED in ALL POOLS.
|
||||
///
|
||||
/// It is the responsibility of the depositor to put these funds into the pool initially. Upon
|
||||
/// unbond, they can never unbond to a value below this amount.
|
||||
///
|
||||
/// It is essentially `max { MinNominatorBond, MinCreateBond, MinJoinBond }`, where the former
|
||||
/// is coming from the staking pallet and the latter two are configured in this pallet.
|
||||
fn depositor_min_bond() -> BalanceOf<T> {
|
||||
T::StakingInterface::minimum_bond()
|
||||
.max(MinCreateBond::<T>::get())
|
||||
.max(MinJoinBond::<T>::get())
|
||||
}
|
||||
/// Remove everything related to the given bonded pool.
|
||||
///
|
||||
/// All sub-pools are also deleted. All accounts are dusted and the leftover of the reward
|
||||
|
||||
@@ -22,6 +22,7 @@ pub fn default_reward_account() -> AccountId {
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static MinJoinBondConfig: Balance = 2;
|
||||
pub static CurrentEra: EraIndex = 0;
|
||||
pub static BondingDuration: EraIndex = 3;
|
||||
pub storage BondedBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
|
||||
@@ -245,6 +246,11 @@ impl ExtBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn min_join_bond(self, min: Balance) -> Self {
|
||||
MinJoinBondConfig::set(min);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn with_check(self, level: u8) -> Self {
|
||||
CheckLevel::set(level);
|
||||
self
|
||||
@@ -261,11 +267,12 @@ impl ExtBuilder {
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> sp_io::TestExternalities {
|
||||
sp_tracing::try_init_simple();
|
||||
let mut storage =
|
||||
frame_system::GenesisConfig::default().build_storage::<Runtime>().unwrap();
|
||||
|
||||
let _ = crate::GenesisConfig::<Runtime> {
|
||||
min_join_bond: 2,
|
||||
min_join_bond: MinJoinBondConfig::get(),
|
||||
min_create_bond: 2,
|
||||
max_pools: Some(2),
|
||||
max_members_per_pool: self.max_members_per_pool,
|
||||
@@ -280,8 +287,8 @@ impl ExtBuilder {
|
||||
frame_system::Pallet::<Runtime>::set_block_number(1);
|
||||
|
||||
// make a pool
|
||||
let amount_to_bond = <Runtime as pools::Config>::StakingInterface::minimum_bond();
|
||||
Balances::make_free_balance_be(&10, amount_to_bond * 2);
|
||||
let amount_to_bond = Pools::depositor_min_bond();
|
||||
Balances::make_free_balance_be(&10, amount_to_bond * 5);
|
||||
assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902));
|
||||
|
||||
let last_pool = LastPoolId::<Runtime>::get();
|
||||
@@ -302,12 +309,13 @@ impl ExtBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) -> Result<(), ()> {
|
||||
pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) {
|
||||
BondedPools::<Runtime>::try_mutate(pool_id, |maybe_bonded_pool| {
|
||||
maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| {
|
||||
bonded_pool.state = state;
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -538,13 +538,13 @@ mod join {
|
||||
StakingMock::set_bonded_balance(Pools::create_bonded_account(1), max_points_to_balance);
|
||||
|
||||
// Cannot join a pool that isn't open
|
||||
unsafe_set_state(123, PoolState::Blocked).unwrap();
|
||||
unsafe_set_state(123, PoolState::Blocked);
|
||||
assert_noop!(
|
||||
Pools::join(Origin::signed(11), max_points_to_balance, 123),
|
||||
Error::<Runtime>::NotOpen
|
||||
);
|
||||
|
||||
unsafe_set_state(123, PoolState::Destroying).unwrap();
|
||||
unsafe_set_state(123, PoolState::Destroying);
|
||||
assert_noop!(
|
||||
Pools::join(Origin::signed(11), max_points_to_balance, 123),
|
||||
Error::<Runtime>::NotOpen
|
||||
@@ -1824,7 +1824,8 @@ mod claim_payout {
|
||||
fn rewards_are_rounded_down_depositor_collects_them() {
|
||||
ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
|
||||
// initial balance of 10.
|
||||
assert_eq!(Balances::free_balance(&10), 5);
|
||||
|
||||
assert_eq!(Balances::free_balance(&10), 35);
|
||||
assert_eq!(
|
||||
Balances::free_balance(&default_reward_account()),
|
||||
Balances::minimum_balance()
|
||||
@@ -1875,7 +1876,7 @@ mod claim_payout {
|
||||
);
|
||||
|
||||
// original ed + ed put into reward account + reward + bond + dust.
|
||||
assert_eq!(Balances::free_balance(&10), 5 + 5 + 13 + 10 + 1);
|
||||
assert_eq!(Balances::free_balance(&10), 35 + 5 + 13 + 10 + 1);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1942,10 +1943,269 @@ mod claim_payout {
|
||||
mod unbond {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn member_unbond_open() {
|
||||
// depositor in pool, pool state open
|
||||
// - member unbond above limit
|
||||
// - member unbonds to 0
|
||||
// - member cannot unbond between within limit and 0
|
||||
ExtBuilder::default()
|
||||
.min_join_bond(10)
|
||||
.add_members(vec![(20, 20)])
|
||||
.build_and_execute(|| {
|
||||
// can unbond to above limit
|
||||
assert_ok!(Pools::unbond(Origin::signed(20), 20, 5));
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 15);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 5);
|
||||
|
||||
// cannot go to below 10:
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(20), 20, 10),
|
||||
Error::<T>::MinimumBondNotMet
|
||||
);
|
||||
|
||||
// but can go to 0
|
||||
assert_ok!(Pools::unbond(Origin::signed(20), 20, 15));
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 0);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 20);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn member_kicked() {
|
||||
// depositor in pool, pool state blocked
|
||||
// - member cannot be kicked to above limit
|
||||
// - member cannot be kicked between within limit and 0
|
||||
// - member kicked to 0
|
||||
ExtBuilder::default()
|
||||
.min_join_bond(10)
|
||||
.add_members(vec![(20, 20)])
|
||||
.build_and_execute(|| {
|
||||
unsafe_set_state(1, PoolState::Blocked);
|
||||
let kicker = DEFAULT_ROLES.state_toggler.unwrap();
|
||||
|
||||
// cannot be kicked to above the limit.
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(kicker), 20, 5),
|
||||
Error::<T>::PartialUnbondNotAllowedPermissionlessly
|
||||
);
|
||||
|
||||
// cannot go to below 10:
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(kicker), 20, 15),
|
||||
Error::<T>::PartialUnbondNotAllowedPermissionlessly
|
||||
);
|
||||
|
||||
// but they themselves can do an unbond
|
||||
assert_ok!(Pools::unbond(Origin::signed(20), 20, 2));
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 18);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 2);
|
||||
|
||||
// can be kicked to 0.
|
||||
assert_ok!(Pools::unbond(Origin::signed(kicker), 20, 18));
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 0);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 20);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn member_unbond_destroying() {
|
||||
// depositor in pool, pool state destroying
|
||||
// - member cannot be permissionlessly unbonded to above limit
|
||||
// - member cannot be permissionlessly unbonded between within limit and 0
|
||||
// - member permissionlessly unbonded to 0
|
||||
ExtBuilder::default()
|
||||
.min_join_bond(10)
|
||||
.add_members(vec![(20, 20)])
|
||||
.build_and_execute(|| {
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
let random = 123;
|
||||
|
||||
// cannot be kicked to above the limit.
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(random), 20, 5),
|
||||
Error::<T>::PartialUnbondNotAllowedPermissionlessly
|
||||
);
|
||||
|
||||
// cannot go to below 10:
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(random), 20, 15),
|
||||
Error::<T>::PartialUnbondNotAllowedPermissionlessly
|
||||
);
|
||||
|
||||
// but they themselves can do an unbond
|
||||
assert_ok!(Pools::unbond(Origin::signed(20), 20, 2));
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 18);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 2);
|
||||
|
||||
// but can go to 0
|
||||
assert_ok!(Pools::unbond(Origin::signed(random), 20, 18));
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 0);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 20);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn depositor_unbond_open() {
|
||||
// depositor in pool, pool state open
|
||||
// - depositor unbonds to above limit
|
||||
// - depositor cannot unbond to below limit or 0
|
||||
ExtBuilder::default().min_join_bond(10).build_and_execute(|| {
|
||||
// give the depositor some extra funds.
|
||||
assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10)));
|
||||
assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);
|
||||
|
||||
// can unbond to above the limit.
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 5));
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 15);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 5);
|
||||
|
||||
// cannot go to below 10:
|
||||
assert_noop!(Pools::unbond(Origin::signed(10), 10, 10), Error::<T>::MinimumBondNotMet);
|
||||
|
||||
// cannot go to 0 either.
|
||||
assert_noop!(Pools::unbond(Origin::signed(10), 10, 15), Error::<T>::MinimumBondNotMet);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn depositor_kick() {
|
||||
// depositor in pool, pool state blocked
|
||||
// - depositor can never be kicked.
|
||||
ExtBuilder::default().min_join_bond(10).build_and_execute(|| {
|
||||
// give the depositor some extra funds.
|
||||
assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10)));
|
||||
assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);
|
||||
|
||||
// set the stage
|
||||
unsafe_set_state(1, PoolState::Blocked);
|
||||
let kicker = DEFAULT_ROLES.state_toggler.unwrap();
|
||||
|
||||
// cannot be kicked to above limit.
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(kicker), 10, 5),
|
||||
Error::<T>::PartialUnbondNotAllowedPermissionlessly
|
||||
);
|
||||
|
||||
// or below the limit
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(kicker), 10, 15),
|
||||
Error::<T>::PartialUnbondNotAllowedPermissionlessly
|
||||
);
|
||||
|
||||
// or 0.
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(kicker), 10, 20),
|
||||
Error::<T>::DoesNotHavePermission
|
||||
);
|
||||
|
||||
// they themselves cannot do it either
|
||||
assert_noop!(Pools::unbond(Origin::signed(10), 10, 20), Error::<T>::MinimumBondNotMet);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn depositor_unbond_destroying_permissionless() {
|
||||
// depositor can never be permissionlessly unbonded.
|
||||
ExtBuilder::default().min_join_bond(10).build_and_execute(|| {
|
||||
// give the depositor some extra funds.
|
||||
assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10)));
|
||||
assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);
|
||||
|
||||
// set the stage
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
let random = 123;
|
||||
|
||||
// cannot be kicked to above limit.
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(random), 10, 5),
|
||||
Error::<T>::PartialUnbondNotAllowedPermissionlessly
|
||||
);
|
||||
|
||||
// or below the limit
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(random), 10, 15),
|
||||
Error::<T>::PartialUnbondNotAllowedPermissionlessly
|
||||
);
|
||||
|
||||
// or 0.
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(random), 10, 20),
|
||||
Error::<T>::DoesNotHavePermission
|
||||
);
|
||||
|
||||
// they themselves can do it in this case though.
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 20));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn depositor_unbond_destroying_not_last_member() {
|
||||
// deposit in pool, pool state destroying
|
||||
// - depositor can never leave if there is another member in the pool.
|
||||
ExtBuilder::default()
|
||||
.min_join_bond(10)
|
||||
.add_members(vec![(20, 20)])
|
||||
.build_and_execute(|| {
|
||||
// give the depositor some extra funds.
|
||||
assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10)));
|
||||
assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);
|
||||
|
||||
// set the stage
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
|
||||
// can go above the limit
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 5));
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 15);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 5);
|
||||
|
||||
// but not below the limit
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(10), 10, 10),
|
||||
Error::<T>::MinimumBondNotMet
|
||||
);
|
||||
|
||||
// and certainly not zero
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(10), 10, 15),
|
||||
Error::<T>::MinimumBondNotMet
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn depositor_unbond_destroying_last_member() {
|
||||
// deposit in pool, pool state destroying
|
||||
// - depositor can unbond to above limit always.
|
||||
// - depositor cannot unbond to below limit if last.
|
||||
// - depositor can unbond to 0 if last and destroying.
|
||||
ExtBuilder::default().min_join_bond(10).build_and_execute(|| {
|
||||
// give the depositor some extra funds.
|
||||
assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10)));
|
||||
assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);
|
||||
|
||||
// set the stage
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
|
||||
// can unbond to above the limit.
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 5));
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 15);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 5);
|
||||
|
||||
// still cannot go to below limit
|
||||
assert_noop!(Pools::unbond(Origin::signed(10), 10, 10), Error::<T>::MinimumBondNotMet);
|
||||
|
||||
// can go to 0 too.
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 15));
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 0);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 20);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbond_of_1_works() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
assert_ok!(fully_unbond_permissioned(10));
|
||||
|
||||
assert_eq!(
|
||||
@@ -2021,7 +2281,7 @@ mod unbond {
|
||||
assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding
|
||||
|
||||
// When
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
assert_ok!(fully_unbond_permissioned(550));
|
||||
|
||||
// Then
|
||||
@@ -2111,7 +2371,7 @@ mod unbond {
|
||||
},
|
||||
},
|
||||
);
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
|
||||
// When
|
||||
let current_era = 1 + TotalUnbondingPools::<Runtime>::get();
|
||||
@@ -2148,7 +2408,7 @@ mod unbond {
|
||||
.add_members(vec![(100, 100), (200, 200)])
|
||||
.build_and_execute(|| {
|
||||
// Given
|
||||
unsafe_set_state(1, PoolState::Blocked).unwrap();
|
||||
unsafe_set_state(1, PoolState::Blocked);
|
||||
let bonded_pool = BondedPool::<Runtime>::get(1).unwrap();
|
||||
assert_eq!(bonded_pool.roles.root.unwrap(), 900);
|
||||
assert_eq!(bonded_pool.roles.nominator.unwrap(), 901);
|
||||
@@ -2216,7 +2476,7 @@ mod unbond {
|
||||
// Scenarios where non-admin accounts can unbond others
|
||||
ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| {
|
||||
// Given the pool is blocked
|
||||
unsafe_set_state(1, PoolState::Blocked).unwrap();
|
||||
unsafe_set_state(1, PoolState::Blocked);
|
||||
|
||||
// A permissionless unbond attempt errors
|
||||
assert_noop!(
|
||||
@@ -2231,16 +2491,17 @@ mod unbond {
|
||||
);
|
||||
|
||||
// Given the pool is destroying
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
|
||||
// The depositor cannot be fully unbonded until they are the last member
|
||||
assert_noop!(
|
||||
Pools::fully_unbond(Origin::signed(10), 10),
|
||||
Error::<Runtime>::NotOnlyPoolMember
|
||||
Error::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
// Any account can unbond a member that is not the depositor
|
||||
assert_ok!(Pools::fully_unbond(Origin::signed(420), 100));
|
||||
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
@@ -2258,7 +2519,7 @@ mod unbond {
|
||||
);
|
||||
|
||||
// Given the pool is blocked
|
||||
unsafe_set_state(1, PoolState::Blocked).unwrap();
|
||||
unsafe_set_state(1, PoolState::Blocked);
|
||||
|
||||
// The depositor cannot be unbonded
|
||||
assert_noop!(
|
||||
@@ -2267,7 +2528,7 @@ mod unbond {
|
||||
);
|
||||
|
||||
// Given the pools is destroying
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
|
||||
// The depositor cannot be unbonded yet.
|
||||
assert_noop!(
|
||||
@@ -2285,8 +2546,13 @@ mod unbond {
|
||||
Error::<Runtime>::PartialUnbondNotAllowedPermissionlessly,
|
||||
);
|
||||
|
||||
// but full unbond works.
|
||||
assert_ok!(Pools::fully_unbond(Origin::signed(420), 10));
|
||||
// depositor can never be unbonded permissionlessly .
|
||||
assert_noop!(
|
||||
Pools::fully_unbond(Origin::signed(420), 10),
|
||||
Error::<T>::DoesNotHavePermission
|
||||
);
|
||||
// but depositor itself can do it.
|
||||
assert_ok!(Pools::fully_unbond(Origin::signed(10), 10));
|
||||
|
||||
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 0);
|
||||
assert_eq!(
|
||||
@@ -2346,6 +2612,12 @@ mod unbond {
|
||||
#[test]
|
||||
fn partial_unbond_era_tracking() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
// to make the depositor capable of withdrawing.
|
||||
StakingMinBond::set(1);
|
||||
MinCreateBond::<T>::set(1);
|
||||
MinJoinBond::<T>::set(1);
|
||||
assert_eq!(Pools::depositor_min_bond(), 1);
|
||||
|
||||
// given
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 10);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 0);
|
||||
@@ -2360,7 +2632,7 @@ mod unbond {
|
||||
assert_eq!(BondingDuration::get(), 3);
|
||||
|
||||
// so the depositor can leave, just keeps the test simpler.
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
|
||||
// when: casual unbond
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 1));
|
||||
@@ -2444,13 +2716,13 @@ mod unbond {
|
||||
);
|
||||
|
||||
// when: unbonding more than our active: error
|
||||
assert_err!(
|
||||
assert_noop!(
|
||||
frame_support::storage::in_storage_layer(|| Pools::unbond(
|
||||
Origin::signed(10),
|
||||
10,
|
||||
5
|
||||
)),
|
||||
Error::<Runtime>::NotEnoughPointsToUnbond
|
||||
Error::<Runtime>::MinimumBondNotMet
|
||||
);
|
||||
// instead:
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 3));
|
||||
@@ -2482,26 +2754,24 @@ mod unbond {
|
||||
|
||||
#[test]
|
||||
fn partial_unbond_max_chunks() {
|
||||
ExtBuilder::default().ed(1).build_and_execute(|| {
|
||||
// so the depositor can leave, just keeps the test simpler.
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
ExtBuilder::default().add_members(vec![(20, 20)]).ed(1).build_and_execute(|| {
|
||||
MaxUnbonding::set(2);
|
||||
|
||||
// given
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 2));
|
||||
assert_ok!(Pools::unbond(Origin::signed(20), 20, 2));
|
||||
CurrentEra::set(1);
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 3));
|
||||
assert_ok!(Pools::unbond(Origin::signed(20), 20, 3));
|
||||
assert_eq!(
|
||||
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
|
||||
PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
|
||||
member_unbonding_eras!(3 => 2, 4 => 3)
|
||||
);
|
||||
|
||||
// when
|
||||
CurrentEra::set(2);
|
||||
assert_err!(
|
||||
assert_noop!(
|
||||
frame_support::storage::in_storage_layer(|| Pools::unbond(
|
||||
Origin::signed(10),
|
||||
10,
|
||||
Origin::signed(20),
|
||||
20,
|
||||
4
|
||||
)),
|
||||
Error::<Runtime>::MaxUnbondingLimit
|
||||
@@ -2509,30 +2779,35 @@ mod unbond {
|
||||
|
||||
// when
|
||||
MaxUnbonding::set(3);
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 1));
|
||||
assert_ok!(Pools::unbond(Origin::signed(20), 20, 1));
|
||||
|
||||
assert_eq!(
|
||||
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
|
||||
PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
|
||||
member_unbonding_eras!(3 => 2, 4 => 3, 5 => 1)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
Event::Created { depositor: 10, pool_id: 1 },
|
||||
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
|
||||
Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2 },
|
||||
Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 },
|
||||
Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 }
|
||||
Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
|
||||
Event::Unbonded { member: 20, pool_id: 1, balance: 2, points: 2 },
|
||||
Event::Unbonded { member: 20, pool_id: 1, balance: 3, points: 3 },
|
||||
Event::Unbonded { member: 20, pool_id: 1, balance: 1, points: 1 }
|
||||
]
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
// depositor can unbond inly up to `MinCreateBond`.
|
||||
// depositor can unbond only up to `MinCreateBond`.
|
||||
#[test]
|
||||
fn depositor_permissioned_partial_unbond() {
|
||||
ExtBuilder::default().ed(1).build_and_execute(|| {
|
||||
// given
|
||||
assert_eq!(MinCreateBond::<Runtime>::get(), 2);
|
||||
StakingMinBond::set(5);
|
||||
assert_eq!(Pools::depositor_min_bond(), 5);
|
||||
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 10);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 0);
|
||||
|
||||
@@ -2544,7 +2819,7 @@ mod unbond {
|
||||
// but not less than 2
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(10), 10, 6),
|
||||
Error::<Runtime>::NotOnlyPoolMember
|
||||
Error::<Runtime>::MinimumBondNotMet
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -2558,7 +2833,6 @@ mod unbond {
|
||||
});
|
||||
}
|
||||
|
||||
// same as above, but the pool is slashed and therefore the depositor cannot partially unbond.
|
||||
#[test]
|
||||
fn depositor_permissioned_partial_unbond_slashed() {
|
||||
ExtBuilder::default().ed(1).build_and_execute(|| {
|
||||
@@ -2573,78 +2847,69 @@ mod unbond {
|
||||
// cannot unbond even 7, because the value of shares is now less.
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(10), 10, 7),
|
||||
Error::<Runtime>::NotOnlyPoolMember
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
Event::Created { depositor: 10, pool_id: 1 },
|
||||
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
|
||||
]
|
||||
Error::<Runtime>::MinimumBondNotMet
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_unbonding_triggers_payout() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
let initial_reward_account = Balances::free_balance(Pools::create_reward_account(1));
|
||||
ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
|
||||
let initial_reward_account = Balances::free_balance(default_reward_account());
|
||||
assert_eq!(initial_reward_account, Balances::minimum_balance());
|
||||
assert_eq!(initial_reward_account, 5);
|
||||
|
||||
// set the pool to destroying so that depositor can leave.
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
|
||||
Balances::make_free_balance_be(
|
||||
&Pools::create_reward_account(1),
|
||||
2 * Balances::minimum_balance(),
|
||||
&default_reward_account(),
|
||||
4 * Balances::minimum_balance(),
|
||||
);
|
||||
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 2));
|
||||
assert_ok!(Pools::unbond(Origin::signed(20), 20, 2));
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
// 2/3 of ed, which is 20's share.
|
||||
Event::Created { depositor: 10, pool_id: 1 },
|
||||
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
|
||||
// exactly equal to ed, all that can be claimed.
|
||||
Event::PaidOut { member: 10, pool_id: 1, payout: 5 },
|
||||
Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2 }
|
||||
Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
|
||||
Event::PaidOut { member: 20, pool_id: 1, payout: 10 },
|
||||
Event::Unbonded { member: 20, pool_id: 1, balance: 2, points: 2 }
|
||||
]
|
||||
);
|
||||
|
||||
CurrentEra::set(1);
|
||||
Balances::make_free_balance_be(
|
||||
&Pools::create_reward_account(1),
|
||||
2 * Balances::minimum_balance(),
|
||||
&default_reward_account(),
|
||||
4 * Balances::minimum_balance(),
|
||||
);
|
||||
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 3));
|
||||
assert_ok!(Pools::unbond(Origin::signed(20), 20, 3));
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
// exactly equal to ed, all that can be claimed.
|
||||
Event::PaidOut { member: 10, pool_id: 1, payout: 5 },
|
||||
Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 }
|
||||
// 2/3 of ed, which is 20's share.
|
||||
Event::PaidOut { member: 20, pool_id: 1, payout: 6 },
|
||||
Event::Unbonded { member: 20, pool_id: 1, points: 3, balance: 3 }
|
||||
]
|
||||
);
|
||||
|
||||
CurrentEra::set(2);
|
||||
Balances::make_free_balance_be(
|
||||
&Pools::create_reward_account(1),
|
||||
2 * Balances::minimum_balance(),
|
||||
&default_reward_account(),
|
||||
4 * Balances::minimum_balance(),
|
||||
);
|
||||
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 5));
|
||||
assert_ok!(Pools::unbond(Origin::signed(20), 20, 5));
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
Event::PaidOut { member: 10, pool_id: 1, payout: 5 },
|
||||
Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5 }
|
||||
Event::PaidOut { member: 20, pool_id: 1, payout: 3 },
|
||||
Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5 }
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
|
||||
PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
|
||||
member_unbonding_eras!(3 => 2, 4 => 3, 5 => 5)
|
||||
);
|
||||
});
|
||||
@@ -2801,7 +3066,7 @@ mod withdraw_unbonded {
|
||||
);
|
||||
|
||||
// now, finally, the depositor can take out its share.
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
assert_ok!(fully_unbond_permissioned(10));
|
||||
|
||||
current_era += 3;
|
||||
@@ -2911,7 +3176,7 @@ mod withdraw_unbonded {
|
||||
assert!(SubPoolsStorage::<Runtime>::get(&1).unwrap().with_era.is_empty());
|
||||
|
||||
// now, finally, the depositor can take out its share.
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
assert_ok!(fully_unbond_permissioned(10));
|
||||
|
||||
// because everyone else has left, the points
|
||||
@@ -2926,7 +3191,7 @@ mod withdraw_unbonded {
|
||||
assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0));
|
||||
|
||||
// then
|
||||
assert_eq!(Balances::free_balance(&10), 10 + 5);
|
||||
assert_eq!(Balances::free_balance(&10), 10 + 35);
|
||||
assert_eq!(Balances::free_balance(&default_bonded_account()), 0);
|
||||
|
||||
// in this test 10 also gets a fair share of the slash, because the slash was
|
||||
@@ -2955,9 +3220,9 @@ mod withdraw_unbonded {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
// Given
|
||||
assert_eq!(Balances::minimum_balance(), 5);
|
||||
assert_eq!(Balances::free_balance(&10), 5);
|
||||
assert_eq!(Balances::free_balance(&10), 35);
|
||||
assert_eq!(Balances::free_balance(&default_bonded_account()), 10);
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
assert_ok!(Pools::fully_unbond(Origin::signed(10), 10));
|
||||
|
||||
// Simulate a slash that is not accounted for in the sub pools.
|
||||
@@ -2974,7 +3239,7 @@ mod withdraw_unbonded {
|
||||
assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0));
|
||||
|
||||
// Then
|
||||
assert_eq!(Balances::free_balance(10), 10 + 5);
|
||||
assert_eq!(Balances::free_balance(10), 10 + 35);
|
||||
assert_eq!(Balances::free_balance(&default_bonded_account()), 0);
|
||||
});
|
||||
}
|
||||
@@ -3054,7 +3319,7 @@ mod withdraw_unbonded {
|
||||
);
|
||||
|
||||
// Given
|
||||
unsafe_set_state(1, PoolState::Blocked).unwrap();
|
||||
unsafe_set_state(1, PoolState::Blocked);
|
||||
|
||||
// Cannot kick as a nominator
|
||||
assert_noop!(
|
||||
@@ -3112,7 +3377,7 @@ mod withdraw_unbonded {
|
||||
);
|
||||
|
||||
// Given
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
|
||||
// Can permissionlesly withdraw a member that is not the depositor
|
||||
assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0));
|
||||
@@ -3137,8 +3402,8 @@ mod withdraw_unbonded {
|
||||
#[test]
|
||||
fn partial_withdraw_unbonded_depositor() {
|
||||
ExtBuilder::default().ed(1).build_and_execute(|| {
|
||||
// so the depositor can leave, just keeps the test simpler.
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10)));
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
|
||||
// given
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 6));
|
||||
@@ -3158,13 +3423,14 @@ mod withdraw_unbonded {
|
||||
}
|
||||
}
|
||||
);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 3);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 13);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 7);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
Event::Created { depositor: 10, pool_id: 1 },
|
||||
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
|
||||
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false },
|
||||
Event::Unbonded { member: 10, pool_id: 1, points: 6, balance: 6 },
|
||||
Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 }
|
||||
]
|
||||
@@ -3368,50 +3634,72 @@ mod withdraw_unbonded {
|
||||
#[test]
|
||||
fn full_multi_step_withdrawing_depositor() {
|
||||
ExtBuilder::default().ed(1).build_and_execute(|| {
|
||||
// given
|
||||
// depositor now has 20, they can unbond to 10.
|
||||
assert_eq!(Pools::depositor_min_bond(), 10);
|
||||
assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10)));
|
||||
|
||||
// now they can.
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 7));
|
||||
|
||||
// progress one era and unbond the leftover.
|
||||
CurrentEra::set(1);
|
||||
unsafe_set_state(1, PoolState::Destroying).unwrap();
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 3));
|
||||
|
||||
assert_eq!(
|
||||
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
|
||||
member_unbonding_eras!(3 => 7, 4 => 3)
|
||||
);
|
||||
|
||||
// they can't unbond to a value below 10 other than 0..
|
||||
assert_noop!(
|
||||
Pools::withdraw_unbonded(Origin::signed(10), 10, 0),
|
||||
Error::<Runtime>::CannotWithdrawAny
|
||||
Pools::unbond(Origin::signed(10), 10, 5),
|
||||
Error::<Runtime>::MinimumBondNotMet
|
||||
);
|
||||
|
||||
// but not even full, because they pool is not yet destroying.
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(10), 10, 10),
|
||||
Error::<Runtime>::MinimumBondNotMet
|
||||
);
|
||||
|
||||
// but now they can.
|
||||
unsafe_set_state(1, PoolState::Destroying);
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(10), 10, 5),
|
||||
Error::<Runtime>::MinimumBondNotMet
|
||||
);
|
||||
assert_ok!(Pools::unbond(Origin::signed(10), 10, 10));
|
||||
|
||||
// now the 7 should be free.
|
||||
CurrentEra::set(3);
|
||||
assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0));
|
||||
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
Event::Created { depositor: 10, pool_id: 1 },
|
||||
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
|
||||
Event::Unbonded { member: 10, pool_id: 1, points: 7, balance: 7 },
|
||||
Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 },
|
||||
Event::Withdrawn { member: 10, pool_id: 1, points: 7, balance: 7 }
|
||||
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false },
|
||||
Event::Unbonded { member: 10, pool_id: 1, balance: 7, points: 7 },
|
||||
Event::Unbonded { member: 10, pool_id: 1, balance: 3, points: 3 },
|
||||
Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10 },
|
||||
Event::Withdrawn { member: 10, pool_id: 1, balance: 7, points: 7 }
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
|
||||
member_unbonding_eras!(4 => 3)
|
||||
member_unbonding_eras!(4 => 13)
|
||||
);
|
||||
|
||||
// the 25 should be free now, and the member removed.
|
||||
// the 13 should be free now, and the member removed.
|
||||
CurrentEra::set(4);
|
||||
assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0));
|
||||
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
Event::Withdrawn { member: 10, pool_id: 1, points: 3, balance: 3 },
|
||||
Event::Withdrawn { member: 10, pool_id: 1, points: 13, balance: 13 },
|
||||
Event::MemberRemoved { pool_id: 1, member: 10 },
|
||||
// the pool is also destroyed now.
|
||||
Event::Destroyed { pool_id: 1 },
|
||||
]
|
||||
);
|
||||
@@ -3640,7 +3928,7 @@ mod set_state {
|
||||
// If the pool is not ok to be open, then anyone can set it to destroying
|
||||
|
||||
// Given
|
||||
unsafe_set_state(1, PoolState::Open).unwrap();
|
||||
unsafe_set_state(1, PoolState::Open);
|
||||
let mut bonded_pool = BondedPool::<Runtime>::get(1).unwrap();
|
||||
bonded_pool.points = 100;
|
||||
bonded_pool.put();
|
||||
@@ -3651,7 +3939,7 @@ mod set_state {
|
||||
|
||||
// Given
|
||||
Balances::make_free_balance_be(&default_bonded_account(), Balance::max_value() / 10);
|
||||
unsafe_set_state(1, PoolState::Open).unwrap();
|
||||
unsafe_set_state(1, PoolState::Open);
|
||||
// When
|
||||
assert_ok!(Pools::set_state(Origin::signed(11), 1, PoolState::Destroying));
|
||||
// Then
|
||||
@@ -3659,7 +3947,7 @@ mod set_state {
|
||||
|
||||
// If the pool is not ok to be open, it cannot be permissionleslly set to a state that
|
||||
// isn't destroying
|
||||
unsafe_set_state(1, PoolState::Open).unwrap();
|
||||
unsafe_set_state(1, PoolState::Open);
|
||||
assert_noop!(
|
||||
Pools::set_state(Origin::signed(11), 1, PoolState::Blocked),
|
||||
Error::<Runtime>::CanNotChangeState
|
||||
@@ -3820,13 +4108,13 @@ mod bond_extra {
|
||||
// given
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10);
|
||||
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 10);
|
||||
assert_eq!(Balances::free_balance(10), 5);
|
||||
assert_eq!(Balances::free_balance(10), 35);
|
||||
|
||||
// when
|
||||
assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::Rewards));
|
||||
|
||||
// then
|
||||
assert_eq!(Balances::free_balance(10), 5);
|
||||
assert_eq!(Balances::free_balance(10), 35);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10 + claimable_reward);
|
||||
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 10 + claimable_reward);
|
||||
|
||||
@@ -3862,14 +4150,14 @@ mod bond_extra {
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20);
|
||||
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30);
|
||||
assert_eq!(Balances::free_balance(10), 5);
|
||||
assert_eq!(Balances::free_balance(10), 35);
|
||||
assert_eq!(Balances::free_balance(20), 20);
|
||||
|
||||
// when
|
||||
assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::Rewards));
|
||||
|
||||
// then
|
||||
assert_eq!(Balances::free_balance(10), 5);
|
||||
assert_eq!(Balances::free_balance(10), 35);
|
||||
// 10's share of the reward is 1/3, since they gave 10/30 of the total shares.
|
||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10 + 1);
|
||||
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30 + 1);
|
||||
|
||||
@@ -72,7 +72,7 @@ fn pool_lifecycle_e2e() {
|
||||
// depositor cannot unbond yet.
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(10), 10, 50),
|
||||
PoolsError::<Runtime>::NotOnlyPoolMember,
|
||||
PoolsError::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
// now the members want to unbond.
|
||||
@@ -103,7 +103,7 @@ fn pool_lifecycle_e2e() {
|
||||
// depositor cannot still unbond
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(10), 10, 50),
|
||||
PoolsError::<Runtime>::NotOnlyPoolMember,
|
||||
PoolsError::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
for e in 1..BondingDuration::get() {
|
||||
@@ -120,7 +120,7 @@ fn pool_lifecycle_e2e() {
|
||||
// depositor cannot still unbond
|
||||
assert_noop!(
|
||||
Pools::unbond(Origin::signed(10), 10, 50),
|
||||
PoolsError::<Runtime>::NotOnlyPoolMember,
|
||||
PoolsError::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
// but members can now withdraw.
|
||||
|
||||
Reference in New Issue
Block a user