use super::*;
use crate::{mock::*, Event};
use frame_support::{assert_err, assert_noop, assert_ok, assert_storage_noop};
use pallet_balances::Event as BEvent;
use sp_runtime::{bounded_btree_map, traits::Dispatchable, FixedU128};
macro_rules! unbonding_pools_with_era {
($($k:expr => $v:expr),* $(,)?) => {{
use sp_std::iter::{Iterator, IntoIterator};
let not_bounded: BTreeMap<_, _> = Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*]));
BoundedBTreeMap::<EraIndex, UnbondPool<T>, TotalUnbondingPools<T>>::try_from(not_bounded).unwrap()
}};
}
macro_rules! member_unbonding_eras {
($( $any:tt )*) => {{
let x: BoundedBTreeMap<EraIndex, Balance, MaxUnbonding> = bounded_btree_map!($( $any )*);
x
}};
}
pub const DEFAULT_ROLES: PoolRoles<AccountId> =
PoolRoles { depositor: 10, root: Some(900), nominator: Some(901), bouncer: Some(902) };
fn deposit_rewards(r: u128) {
let b = Currency::free_balance(&default_reward_account()).checked_add(r).unwrap();
Currency::set_balance(&default_reward_account(), b);
}
fn remove_rewards(r: u128) {
let b = Currency::free_balance(&default_reward_account()).checked_sub(r).unwrap();
Currency::set_balance(&default_reward_account(), b);
}
#[test]
fn test_setup_works() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(BondedPools::<Runtime>::count(), 1);
assert_eq!(RewardPools::<Runtime>::count(), 1);
assert_eq!(SubPoolsStorage::<Runtime>::count(), 0);
assert_eq!(PoolMembers::<Runtime>::count(), 1);
assert_eq!(StakingMock::bonding_duration(), 3);
assert!(Metadata::<T>::contains_key(1));
assert_eq!(TotalValueLocked::<T>::get(), 10);
let last_pool = LastPoolId::<Runtime>::get();
assert_eq!(
BondedPool::<Runtime>::get(last_pool).unwrap(),
BondedPool::<Runtime> {
id: last_pool,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 1,
points: 10,
roles: DEFAULT_ROLES,
state: PoolState::Open,
},
}
);
assert_eq!(
RewardPools::<Runtime>::get(last_pool).unwrap(),
RewardPool::<Runtime> {
last_recorded_reward_counter: Zero::zero(),
last_recorded_total_payouts: 0,
total_rewards_claimed: 0,
total_commission_claimed: 0,
total_commission_pending: 0,
}
);
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap(),
PoolMember::<Runtime> { pool_id: last_pool, points: 10, ..Default::default() }
);
let bonded_account = Pools::create_bonded_account(last_pool);
let reward_account = Pools::create_reward_account(last_pool);
assert_eq!(StakingMock::active_stake(&bonded_account).unwrap(), 10);
assert_eq!(StakingMock::total_stake(&bonded_account).unwrap(), 10);
assert!(Nominations::get().is_none());
assert_eq!(Currency::free_balance(&reward_account), Currency::minimum_balance());
})
}
mod bonded_pool {
use super::*;
#[test]
fn balance_to_point_works() {
ExtBuilder::default().build_and_execute(|| {
let mut bonded_pool = BondedPool::<Runtime> {
id: 123123,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 1,
points: 100,
roles: DEFAULT_ROLES,
state: PoolState::Open,
},
};
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
assert_eq!(bonded_pool.balance_to_point(10), 10);
assert_eq!(bonded_pool.balance_to_point(0), 0);
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 50);
assert_eq!(bonded_pool.balance_to_point(10), 20);
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
bonded_pool.points = 50;
assert_eq!(bonded_pool.balance_to_point(10), 5);
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0);
bonded_pool.points = 100;
assert_eq!(bonded_pool.balance_to_point(10), 100 * 10);
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
bonded_pool.points = 0;
assert_eq!(bonded_pool.balance_to_point(10), 10);
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 30);
bonded_pool.points = 100;
assert_eq!(bonded_pool.balance_to_point(10), 33);
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 300);
bonded_pool.points = 200;
assert_eq!(bonded_pool.balance_to_point(10), 6);
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 900);
bonded_pool.points = 400;
assert_eq!(bonded_pool.balance_to_point(90), 40);
})
}
#[test]
fn points_to_balance_works() {
ExtBuilder::default().build_and_execute(|| {
let mut bonded_pool = BondedPool::<Runtime> {
id: 123123,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 1,
points: 100,
roles: DEFAULT_ROLES,
state: PoolState::Open,
},
};
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
assert_eq!(bonded_pool.points_to_balance(10), 10);
assert_eq!(bonded_pool.points_to_balance(0), 0);
bonded_pool.points = 50;
assert_eq!(bonded_pool.points_to_balance(10), 20);
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
bonded_pool.points = 0;
assert_eq!(bonded_pool.points_to_balance(10), 0);
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0);
bonded_pool.points = 100;
assert_eq!(bonded_pool.points_to_balance(10), 0);
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
bonded_pool.points = 30;
assert_eq!(bonded_pool.points_to_balance(10), 33);
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 200);
bonded_pool.points = 300;
assert_eq!(bonded_pool.points_to_balance(10), 6);
StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 400);
bonded_pool.points = 900;
assert_eq!(bonded_pool.points_to_balance(90), 40);
})
}
#[test]
fn api_points_to_balance_works() {
ExtBuilder::default().build_and_execute(|| {
assert!(BondedPool::<Runtime>::get(1).is_some());
assert_eq!(Pallet::<Runtime>::api_points_to_balance(1, 10), 10);
StakingMock::slash_by(1, Pools::depositor_min_bond() / 2);
assert_eq!(Pallet::<Runtime>::api_points_to_balance(1, 10), 5);
assert_eq!(BondedPool::<Runtime>::get(2), None);
assert_eq!(Pallet::<Runtime>::api_points_to_balance(2, 10), 0);
})
}
#[test]
fn api_balance_to_points_works() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(Pallet::<Runtime>::api_balance_to_points(1, 0), 0);
assert_eq!(Pallet::<Runtime>::api_balance_to_points(1, 10), 10);
StakingMock::slash_by(1, Pools::depositor_min_bond() / 2);
assert_eq!(Pallet::<Runtime>::api_balance_to_points(1, 10), 20);
assert_eq!(BondedPool::<Runtime>::get(2), None);
assert_eq!(Pallet::<Runtime>::api_points_to_balance(2, 10), 0);
})
}
#[test]
fn ok_to_join_with_works() {
ExtBuilder::default().build_and_execute(|| {
let pool = BondedPool::<Runtime> {
id: 123,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 1,
points: 100,
roles: DEFAULT_ROLES,
state: PoolState::Open,
},
};
let max_points_to_balance: u128 =
<<Runtime as Config>::MaxPointsToBalance as Get<u8>>::get().into();
StakingMock::set_bonded_balance(pool.bonded_account(), 0);
assert_noop!(pool.ok_to_join(), Error::<Runtime>::OverflowRisk);
StakingMock::set_bonded_balance(
pool.bonded_account(),
max_points_to_balance.saturating_add(1),
);
assert_ok!(pool.ok_to_join());
StakingMock::set_bonded_balance(pool.bonded_account(), max_points_to_balance);
assert_noop!(pool.ok_to_join(), Error::<Runtime>::OverflowRisk);
StakingMock::set_bonded_balance(
pool.bonded_account(),
Balance::MAX / max_points_to_balance,
);
StakingMock::set_bonded_balance(
pool.bonded_account(),
Balance::MAX / max_points_to_balance - 1,
);
assert_ok!(pool.ok_to_join());
});
}
}
mod reward_pool {
use super::*;
use crate::mock::RewardImbalance::{Deficit, Surplus};
#[test]
fn ed_change_causes_reward_deficit() {
ExtBuilder::default().max_members_per_pool(Some(5)).build_and_execute(|| {
ExistentialDeposit::set(5);
Currency::set_balance(&11, 500);
assert_ok!(Pools::join(RuntimeOrigin::signed(11), 90, 1));
assert_eq!(pending_rewards_for_delegator(11), 0);
deposit_rewards(100);
assert_eq!(pending_rewards_for_delegator(11), 90);
assert_eq!(pending_rewards_for_delegator(10), 10);
assert_eq!(reward_imbalance(1), Surplus(0));
Currency::set_balance(&12, 500);
assert_ok!(Pools::join(RuntimeOrigin::signed(12), 100, 1));
let bonded_pool = BondedPools::<Runtime>::get(1).unwrap();
let reward_pool = RewardPools::<Runtime>::get(1).unwrap();
assert_eq!(
reward_pool.last_recorded_reward_counter,
reward_pool
.current_reward_counter(1, bonded_pool.points, Perbill::zero())
.unwrap()
.0
);
let reward_pool_1 = RewardPools::<Runtime>::get(1).unwrap();
ExistentialDeposit::set(50);
assert_eq!(reward_imbalance(1), Deficit(45));
Currency::set_balance(&13, 500);
assert_ok!(Pools::join(RuntimeOrigin::signed(13), 100, 1));
assert_eq!(reward_imbalance(1), Deficit(45));
let reward_pool_2 = RewardPools::<Runtime>::get(1).unwrap();
assert_eq!(
reward_pool_1.last_recorded_total_payouts,
reward_pool_2.last_recorded_total_payouts
);
deposit_rewards(10);
assert_eq!(reward_imbalance(1), Deficit(35));
deposit_rewards(35);
assert_eq!(reward_imbalance(1), Surplus(0));
assert_ok!(Currency::mint_into(&10, 45));
assert_ok!(Pools::adjust_pool_deposit(RuntimeOrigin::signed(10), 1));
});
}
#[test]
fn ed_adjust_fixes_reward_deficit() {
ExtBuilder::default().max_members_per_pool(Some(5)).build_and_execute(|| {
ExistentialDeposit::set(5);
Currency::set_balance(&11, 500);
assert_ok!(Pools::join(RuntimeOrigin::signed(11), 90, 1));
deposit_rewards(100);
Currency::set_balance(&12, 500);
assert_ok!(Pools::join(RuntimeOrigin::signed(12), 10, 1));
ExistentialDeposit::set(50);
assert_eq!(reward_imbalance(1), Deficit(45));
pool_events_since_last_call();
assert_ok!(Currency::mint_into(&99, 100));
let pre_balance = Currency::free_balance(&99);
assert_ok!(Pools::adjust_pool_deposit(RuntimeOrigin::signed(99), 1));
assert_eq!(Currency::free_balance(&99), pre_balance - 45);
assert_eq!(reward_imbalance(1), Surplus(0));
assert_eq!(
pool_events_since_last_call(),
vec![Event::MinBalanceDeficitAdjusted { pool_id: 1, amount: 45 },]
);
assert_err!(
Pools::adjust_pool_deposit(RuntimeOrigin::signed(10), 1),
Error::<T>::NothingToAdjust
);
ExistentialDeposit::set(5);
let pre_balance = Currency::free_balance(&100);
assert_ok!(Pools::adjust_pool_deposit(RuntimeOrigin::signed(100), 1));
assert_eq!(Currency::free_balance(&100), pre_balance + 45);
assert_eq!(
pool_events_since_last_call(),
vec![Event::MinBalanceExcessAdjusted { pool_id: 1, amount: 45 },]
);
});
}
#[test]
fn topping_up_does_not_work_for_pools_with_no_deficit() {
ExtBuilder::default().max_members_per_pool(Some(5)).build_and_execute(|| {
Currency::set_balance(&11, 500);
assert_ok!(Pools::join(RuntimeOrigin::signed(11), 90, 1));
deposit_rewards(100);
assert_eq!(reward_imbalance(1), Surplus(0));
assert_err!(
Pools::adjust_pool_deposit(RuntimeOrigin::signed(10), 1),
Error::<T>::NothingToAdjust
);
});
}
}
mod unbond_pool {
use super::*;
#[test]
fn points_to_issue_works() {
ExtBuilder::default().build_and_execute(|| {
let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 100 };
assert_eq!(unbond_pool.balance_to_point(10), 10);
assert_eq!(unbond_pool.balance_to_point(0), 0);
let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 50 };
assert_eq!(unbond_pool.balance_to_point(10), 20);
let unbond_pool = UnbondPool::<Runtime> { points: 50, balance: 100 };
assert_eq!(unbond_pool.balance_to_point(10), 5);
let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 0 };
assert_eq!(unbond_pool.balance_to_point(10), 100 * 10);
let unbond_pool = UnbondPool::<Runtime> { points: 0, balance: 100 };
assert_eq!(unbond_pool.balance_to_point(10), 10);
let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 30 };
assert_eq!(unbond_pool.balance_to_point(10), 33);
let unbond_pool = UnbondPool::<Runtime> { points: 200, balance: 300 };
assert_eq!(unbond_pool.balance_to_point(10), 6);
let unbond_pool = UnbondPool::<Runtime> { points: 400, balance: 900 };
assert_eq!(unbond_pool.balance_to_point(90), 40);
})
}
#[test]
fn balance_to_unbond_works() {
let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 100 };
assert_eq!(unbond_pool.point_to_balance(10), 10);
assert_eq!(unbond_pool.point_to_balance(0), 0);
let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 50 };
assert_eq!(unbond_pool.point_to_balance(10), 5);
let unbond_pool = UnbondPool::<Runtime> { points: 50, balance: 100 };
assert_eq!(unbond_pool.point_to_balance(10), 20);
let unbond_pool = UnbondPool::<Runtime> { points: 0, balance: 100 };
assert_eq!(unbond_pool.point_to_balance(10), 0);
let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 0 };
assert_eq!(unbond_pool.point_to_balance(10), 0);
let unbond_pool = UnbondPool::<Runtime> { points: 30, balance: 100 };
assert_eq!(unbond_pool.point_to_balance(10), 33);
let unbond_pool = UnbondPool::<Runtime> { points: 300, balance: 200 };
assert_eq!(unbond_pool.point_to_balance(10), 6);
let unbond_pool = UnbondPool::<Runtime> { points: 900, balance: 400 };
assert_eq!(unbond_pool.point_to_balance(90), 40);
}
}
mod sub_pools {
use super::*;
#[test]
fn maybe_merge_pools_works() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(TotalUnbondingPools::<Runtime>::get(), 5);
assert_eq!(BondingDuration::get(), 3);
assert_eq!(PostUnbondingPoolsWindow::get(), 2);
let mut sub_pool_0 = SubPools::<Runtime> {
no_era: UnbondPool::<Runtime>::default(),
with_era: unbonding_pools_with_era! {
0 => UnbondPool::<Runtime> { points: 10, balance: 10 },
1 => UnbondPool::<Runtime> { points: 10, balance: 10 },
2 => UnbondPool::<Runtime> { points: 20, balance: 20 },
3 => UnbondPool::<Runtime> { points: 30, balance: 30 },
4 => UnbondPool::<Runtime> { points: 40, balance: 40 },
},
};
let sub_pool_1 = sub_pool_0.clone().maybe_merge_pools(0);
assert_eq!(sub_pool_1, sub_pool_0);
let sub_pool_1 = sub_pool_1.maybe_merge_pools(1);
assert_eq!(sub_pool_1, sub_pool_0);
let mut sub_pool_1 = sub_pool_1.maybe_merge_pools(2);
sub_pool_0.no_era = sub_pool_0.with_era.remove(&0).unwrap();
assert_eq!(sub_pool_1, sub_pool_0);
sub_pool_1
.with_era
.try_insert(5, UnbondPool::<Runtime> { points: 50, balance: 50 })
.unwrap();
sub_pool_0
.with_era
.try_insert(5, UnbondPool::<Runtime> { points: 50, balance: 50 })
.unwrap();
let sub_pool_2 = sub_pool_1.maybe_merge_pools(3);
let era_1_pool = sub_pool_0.with_era.remove(&1).unwrap();
sub_pool_0.no_era.points += era_1_pool.points;
sub_pool_0.no_era.balance += era_1_pool.balance;
assert_eq!(sub_pool_2, sub_pool_0);
let sub_pool_3 = sub_pool_2.maybe_merge_pools(7);
for era in 2..=5 {
let to_merge = sub_pool_0.with_era.remove(&era).unwrap();
sub_pool_0.no_era.points += to_merge.points;
sub_pool_0.no_era.balance += to_merge.balance;
}
assert_eq!(sub_pool_3, sub_pool_0);
});
}
}
mod join {
use sp_runtime::TokenError;
use super::*;
#[test]
fn join_works() {
let bonded = |points, member_counter| BondedPool::<Runtime> {
id: 1,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter,
points,
roles: DEFAULT_ROLES,
state: PoolState::Open,
},
};
ExtBuilder::default().with_check(0).build_and_execute(|| {
Currency::set_balance(&11, ExistentialDeposit::get() + 2);
assert!(!PoolMembers::<Runtime>::contains_key(11));
assert_eq!(TotalValueLocked::<T>::get(), 10);
assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true },
]
);
assert_eq!(TotalValueLocked::<T>::get(), 12);
assert_eq!(
PoolMembers::<Runtime>::get(11).unwrap(),
PoolMember::<Runtime> { pool_id: 1, points: 2, ..Default::default() }
);
assert_eq!(BondedPool::<Runtime>::get(1).unwrap(), bonded(12, 2));
StakingMock::slash_by(1, 6);
Currency::set_balance(&12, ExistentialDeposit::get() + 12);
assert!(!PoolMembers::<Runtime>::contains_key(12));
assert_ok!(Pools::join(RuntimeOrigin::signed(12), 12, 1));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PoolSlashed { pool_id: 1, balance: 6 },
Event::Bonded { member: 12, pool_id: 1, bonded: 12, joined: true }
]
);
assert_eq!(TotalValueLocked::<T>::get(), 18);
assert_eq!(
PoolMembers::<Runtime>::get(12).unwrap(),
PoolMember::<Runtime> { pool_id: 1, points: 24, ..Default::default() }
);
assert_eq!(BondedPool::<Runtime>::get(1).unwrap(), bonded(12 + 24, 3));
});
}
#[test]
fn join_errors_correctly() {
ExtBuilder::default().with_check(0).build_and_execute(|| {
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().pool_id, 1);
assert_noop!(
Pools::join(RuntimeOrigin::signed(10), 420, 123),
Error::<Runtime>::AccountBelongsToOtherPool
);
assert_noop!(
Pools::join(RuntimeOrigin::signed(11), 420, 123),
Error::<Runtime>::PoolNotFound
);
StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 0);
assert_noop!(
Pools::join(RuntimeOrigin::signed(11), 420, 1),
Error::<Runtime>::OverflowRisk
);
BondedPool::<Runtime> {
id: 123,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 1,
points: 100,
roles: DEFAULT_ROLES,
state: PoolState::Open,
},
}
.put();
RewardPools::<Runtime>::insert(123, RewardPool::<Runtime> { ..Default::default() });
let max_points_to_balance: u128 =
<<Runtime as Config>::MaxPointsToBalance as Get<u8>>::get().into();
StakingMock::set_bonded_balance(
Pools::create_bonded_account(123),
max_points_to_balance,
);
assert_noop!(
Pools::join(RuntimeOrigin::signed(11), 420, 123),
Error::<Runtime>::OverflowRisk
);
StakingMock::set_bonded_balance(
Pools::create_bonded_account(123),
Balance::MAX / max_points_to_balance,
);
assert_noop!(
Pools::join(RuntimeOrigin::signed(11), 5, 123),
TokenError::FundsUnavailable,
);
StakingMock::set_bonded_balance(Pools::create_bonded_account(1), max_points_to_balance);
unsafe_set_state(123, PoolState::Blocked);
assert_noop!(
Pools::join(RuntimeOrigin::signed(11), max_points_to_balance, 123),
Error::<Runtime>::NotOpen
);
unsafe_set_state(123, PoolState::Destroying);
assert_noop!(
Pools::join(RuntimeOrigin::signed(11), max_points_to_balance, 123),
Error::<Runtime>::NotOpen
);
MinJoinBond::<Runtime>::put(100);
assert_noop!(
Pools::join(RuntimeOrigin::signed(11), 99, 123),
Error::<Runtime>::MinimumBondNotMet
);
});
}
#[test]
#[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))]
#[cfg_attr(not(debug_assertions), should_panic)]
fn join_panics_when_reward_pool_not_found() {
ExtBuilder::default().build_and_execute(|| {
StakingMock::set_bonded_balance(Pools::create_bonded_account(123), 100);
BondedPool::<Runtime> {
id: 123,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 1,
points: 100,
roles: DEFAULT_ROLES,
state: PoolState::Open,
},
}
.put();
let _ = Pools::join(RuntimeOrigin::signed(11), 420, 123);
});
}
#[test]
fn join_max_member_limits_are_respected() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(MaxPoolMembersPerPool::<Runtime>::get(), Some(3));
for i in 1..3 {
let account = i + 100;
Currency::set_balance(&account, 100 + Currency::minimum_balance());
assert_ok!(Pools::join(RuntimeOrigin::signed(account), 100, 1));
}
Currency::set_balance(&103, 100 + Currency::minimum_balance());
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: 101, pool_id: 1, bonded: 100, joined: true },
Event::Bonded { member: 102, pool_id: 1, bonded: 100, joined: true }
]
);
assert_noop!(
Pools::join(RuntimeOrigin::signed(103), 100, 1),
Error::<Runtime>::MaxPoolMembers
);
assert_eq!(PoolMembers::<Runtime>::count(), 3);
assert_eq!(MaxPoolMembers::<Runtime>::get(), Some(4));
Currency::set_balance(&104, 100 + Currency::minimum_balance());
assert_ok!(Pools::create(RuntimeOrigin::signed(104), 100, 104, 104, 104));
let pool_account = BondedPools::<Runtime>::iter()
.find(|(_, bonded_pool)| bonded_pool.roles.depositor == 104)
.map(|(pool_account, _)| pool_account)
.unwrap();
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 104, pool_id: 2 },
Event::Bonded { member: 104, pool_id: 2, bonded: 100, joined: true }
]
);
assert_noop!(
Pools::join(RuntimeOrigin::signed(103), 100, pool_account),
Error::<Runtime>::MaxPoolMembers
);
});
}
}
mod claim_payout {
use super::*;
fn del(points: Balance, last_recorded_reward_counter: u128) -> PoolMember<Runtime> {
PoolMember {
pool_id: 1,
points,
last_recorded_reward_counter: last_recorded_reward_counter.into(),
unbonding_eras: Default::default(),
}
}
fn del_float(points: Balance, last_recorded_reward_counter: f64) -> PoolMember<Runtime> {
PoolMember {
pool_id: 1,
points,
last_recorded_reward_counter: RewardCounter::from_float(last_recorded_reward_counter),
unbonding_eras: Default::default(),
}
}
fn rew(
last_recorded_reward_counter: u128,
last_recorded_total_payouts: Balance,
total_rewards_claimed: Balance,
) -> RewardPool<Runtime> {
RewardPool {
last_recorded_reward_counter: last_recorded_reward_counter.into(),
last_recorded_total_payouts,
total_rewards_claimed,
total_commission_claimed: 0,
total_commission_pending: 0,
}
}
#[test]
fn claim_payout_works() {
ExtBuilder::default()
.add_members(vec![(40, 40), (50, 50)])
.build_and_execute(|| {
Currency::set_balance(&10, 0);
Currency::set_balance(&40, 0);
Currency::set_balance(&50, 0);
let ed = Currency::minimum_balance();
assert_eq!(Currency::free_balance(&default_reward_account()), ed);
deposit_rewards(100);
let _ = pool_events_since_last_call();
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 },]
);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap(), del(10, 1));
assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 10));
assert_eq!(Currency::free_balance(&10), 10);
assert_eq!(Currency::free_balance(&default_reward_account()), ed + 90);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(40)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }]
);
assert_eq!(PoolMembers::<Runtime>::get(40).unwrap(), del(40, 1));
assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 50));
assert_eq!(Currency::free_balance(&40), 40);
assert_eq!(Currency::free_balance(&default_reward_account()), ed + 50);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(50)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }]
);
assert_eq!(PoolMembers::<Runtime>::get(50).unwrap(), del(50, 1));
assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 100));
assert_eq!(Currency::free_balance(&50), 50);
assert_eq!(Currency::free_balance(&default_reward_account()), ed);
deposit_rewards(50);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }]
);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap(), del_float(10, 1.5));
assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 105));
assert_eq!(Currency::free_balance(&10), 10 + 5);
assert_eq!(Currency::free_balance(&default_reward_account()), ed + 45);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(40)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }]
);
assert_eq!(PoolMembers::<Runtime>::get(40).unwrap(), del_float(40, 1.5));
assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 125));
assert_eq!(Currency::free_balance(&40), 40 + 20);
assert_eq!(Currency::free_balance(&default_reward_account()), ed + 25);
deposit_rewards(50);
assert_eq!(Currency::free_balance(&default_reward_account()), ed + 75);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(50)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }]
);
assert_eq!(PoolMembers::<Runtime>::get(50).unwrap(), del_float(50, 2.0));
assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 175));
assert_eq!(Currency::free_balance(&50), 50 + 50);
assert_eq!(Currency::free_balance(&default_reward_account()), ed + 25);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }]
);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap(), del(10, 2));
assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 180));
assert_eq!(Currency::free_balance(&10), 15 + 5);
assert_eq!(Currency::free_balance(&default_reward_account()), ed + 20);
deposit_rewards(400);
assert_eq!(Currency::free_balance(&default_reward_account()), ed + 420);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 40 }]
);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap(), del(10, 6));
assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 220));
assert_eq!(Currency::free_balance(&10), 20 + 40);
assert_eq!(Currency::free_balance(&default_reward_account()), ed + 380);
deposit_rewards(20);
assert_eq!(Currency::free_balance(&default_reward_account()), ed + 400);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 2 }]
);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap(), del_float(10, 6.2));
assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 222));
assert_eq!(Currency::free_balance(&10), 60 + 2);
assert_eq!(Currency::free_balance(&default_reward_account()), ed + 398);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(40)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 40, pool_id: 1, payout: 188 }]
);
assert_eq!(PoolMembers::<Runtime>::get(40).unwrap(), del_float(40, 6.2));
assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 410));
assert_eq!(Currency::free_balance(&40), 60 + 188);
assert_eq!(Currency::free_balance(&default_reward_account()), ed + 210);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(50)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 50, pool_id: 1, payout: 210 }]
);
assert_eq!(PoolMembers::<Runtime>::get(50).unwrap(), del_float(50, 6.2));
assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 620));
assert_eq!(Currency::free_balance(&50), 100 + 210);
assert_eq!(Currency::free_balance(&default_reward_account()), ed);
});
}
#[test]
fn reward_payout_errors_if_a_member_is_fully_unbonding() {
ExtBuilder::default().add_members(vec![(11, 11)]).build_and_execute(|| {
assert_ok!(fully_unbond_permissioned(11));
assert_noop!(
Pools::claim_payout(RuntimeOrigin::signed(11)),
Error::<Runtime>::FullyUnbonding
);
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: 11, pool_id: 1, bonded: 11, joined: true },
Event::Unbonded { member: 11, pool_id: 1, points: 11, balance: 11, era: 3 }
]
);
});
}
#[test]
fn claim_payout_bounds_commission_above_global() {
ExtBuilder::default().build_and_execute(|| {
let (mut member, bonded_pool, mut reward_pool) =
Pools::get_member_with_pools(&10).unwrap();
let _ = Currency::set_balance(&2, 5);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
bonded_pool.id,
Some((Perbill::from_percent(75), 2)),
));
GlobalMaxCommission::<Runtime>::set(Some(Perbill::from_percent(50)));
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::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(75), 2))
}
]
);
deposit_rewards(10);
assert_ok!(Pools::do_reward_payout(
&10,
&mut member,
&mut BondedPool::<Runtime>::get(1).unwrap(),
&mut reward_pool
));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 },]
);
})
}
#[test]
fn do_reward_payout_works_with_a_pool_of_1() {
let del = |last_recorded_reward_counter| del_float(10, last_recorded_reward_counter);
ExtBuilder::default().build_and_execute(|| {
let (mut member, mut bonded_pool, mut reward_pool) =
Pools::get_member_with_pools(&10).unwrap();
let ed = Currency::minimum_balance();
let payout =
Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(payout, 0);
assert_eq!(member, del(0.0));
assert_eq!(reward_pool, rew(0, 0, 0));
deposit_rewards(5);
let payout =
Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool)
.unwrap();
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::PaidOut { member: 10, pool_id: 1, payout: 5 }
]
);
assert_eq!(payout, 5);
assert_eq!(reward_pool, rew(0, 0, 5));
assert_eq!(member, del(0.5));
deposit_rewards(10);
let payout =
Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }]
);
assert_eq!(payout, 10);
assert_eq!(reward_pool, rew(0, 0, 15));
assert_eq!(member, del(1.5));
Currency::set_balance(&default_reward_account(), ed);
let payout =
Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(pool_events_since_last_call(), vec![]);
assert_eq!(payout, 0);
assert_eq!(reward_pool, rew(0, 0, 15));
assert_eq!(member, del(1.5));
});
}
#[test]
fn do_reward_payout_works_with_a_pool_of_3() {
ExtBuilder::default()
.add_members(vec![(40, 40), (50, 50)])
.build_and_execute(|| {
let mut bonded_pool = BondedPool::<Runtime>::get(1).unwrap();
let mut reward_pool = RewardPools::<Runtime>::get(1).unwrap();
let mut del_10 = PoolMembers::<Runtime>::get(10).unwrap();
let mut del_40 = PoolMembers::<Runtime>::get(40).unwrap();
let mut del_50 = PoolMembers::<Runtime>::get(50).unwrap();
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true },
Event::Bonded { member: 50, pool_id: 1, bonded: 50, joined: true }
]
);
assert_eq!(del_50.points + del_40.points + del_10.points, 100);
assert_eq!(bonded_pool.points, 100);
deposit_rewards(100);
let payout =
Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }]
);
assert_eq!(payout, 10);
assert_eq!(del_10, del(10, 1));
assert_eq!(reward_pool, rew(0, 0, 10));
let payout =
Pools::do_reward_payout(&40, &mut del_40, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }]
);
assert_eq!(payout, 40);
assert_eq!(del_40, del(40, 1));
assert_eq!(reward_pool, rew(0, 0, 50));
let payout =
Pools::do_reward_payout(&50, &mut del_50, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }]
);
assert_eq!(payout, 50);
assert_eq!(del_50, del(50, 1));
assert_eq!(reward_pool, rew(0, 0, 100));
deposit_rewards(50);
let payout =
Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }]
);
assert_eq!(payout, 5);
assert_eq!(del_10, del_float(10, 1.5));
assert_eq!(reward_pool, rew(0, 0, 105));
let payout =
Pools::do_reward_payout(&40, &mut del_40, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }]
);
assert_eq!(payout, 20);
assert_eq!(del_40, del_float(40, 1.5));
assert_eq!(reward_pool, rew(0, 0, 125));
deposit_rewards(50);
let payout =
Pools::do_reward_payout(&50, &mut del_50, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }]
);
assert_eq!(payout, 50);
assert_eq!(del_50, del_float(50, 2.0));
assert_eq!(reward_pool, rew(0, 0, 175));
let payout =
Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }]
);
assert_eq!(payout, 5);
assert_eq!(del_10, del_float(10, 2.0));
assert_eq!(reward_pool, rew(0, 0, 180));
deposit_rewards(400);
let payout =
Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 40 }]
);
assert_eq!(payout, 40);
assert_eq!(del_10, del_float(10, 6.0));
assert_eq!(reward_pool, rew(0, 0, 220));
deposit_rewards(20);
let payout =
Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(payout, 2);
assert_eq!(del_10, del_float(10, 6.2));
assert_eq!(reward_pool, rew(0, 0, 222));
let payout =
Pools::do_reward_payout(&40, &mut del_40, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(payout, 188); assert_eq!(del_40, del_float(40, 6.2));
assert_eq!(reward_pool, rew(0, 0, 410));
let payout =
Pools::do_reward_payout(&50, &mut del_50, &mut bonded_pool, &mut reward_pool)
.unwrap();
assert_eq!(payout, 210); assert_eq!(del_50, del_float(50, 6.2));
assert_eq!(reward_pool, rew(0, 0, 620));
});
}
#[test]
fn rewards_distribution_is_fair_basic() {
ExtBuilder::default().build_and_execute(|| {
deposit_rewards(10);
Currency::set_balance(&20, Currency::minimum_balance() + 10);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
deposit_rewards(20);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
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: 20, pool_id: 1, bonded: 10, joined: true },
Event::PaidOut { member: 10, pool_id: 1, payout: 20 },
Event::PaidOut { member: 20, pool_id: 1, payout: 10 },
]
);
deposit_rewards(20);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id: 1, payout: 10 },
Event::PaidOut { member: 20, pool_id: 1, payout: 10 },
]
);
});
}
#[test]
fn rewards_distribution_is_fair_basic_with_fractions() {
ExtBuilder::default().build_and_execute(|| {
deposit_rewards(3);
Currency::set_balance(&20, Currency::minimum_balance() + 10);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
deposit_rewards(6);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
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: 20, pool_id: 1, bonded: 10, joined: true },
Event::PaidOut { member: 10, pool_id: 1, payout: 3 + 3 },
Event::PaidOut { member: 20, pool_id: 1, payout: 3 },
]
);
deposit_rewards(8);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id: 1, payout: 4 },
Event::PaidOut { member: 20, pool_id: 1, payout: 4 },
]
);
deposit_rewards(7);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id: 1, payout: 3 },
Event::PaidOut { member: 20, pool_id: 1, payout: 3 },
]
);
});
}
#[test]
fn rewards_distribution_is_fair_3() {
ExtBuilder::default().build_and_execute(|| {
let ed = Currency::minimum_balance();
deposit_rewards(30);
Currency::set_balance(&20, ed + 10);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
deposit_rewards(100);
Currency::set_balance(&30, ed + 10);
assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));
deposit_rewards(60);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));
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: 20, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true },
Event::PaidOut { member: 10, pool_id: 1, payout: 30 + 100 / 2 + 60 / 3 },
Event::PaidOut { member: 20, pool_id: 1, payout: 100 / 2 + 60 / 3 },
Event::PaidOut { member: 30, pool_id: 1, payout: 60 / 3 },
]
);
deposit_rewards(30);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id: 1, payout: 10 },
Event::PaidOut { member: 20, pool_id: 1, payout: 10 },
Event::PaidOut { member: 30, pool_id: 1, payout: 10 },
]
);
});
}
#[test]
fn pending_rewards_per_member_works() {
ExtBuilder::default().build_and_execute(|| {
let ed = Currency::minimum_balance();
assert_eq!(Pools::api_pending_rewards(10), Some(0));
deposit_rewards(30);
assert_eq!(Pools::api_pending_rewards(10), Some(30));
assert_eq!(Pools::api_pending_rewards(20), None);
Currency::set_balance(&20, ed + 10);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
assert_eq!(Pools::api_pending_rewards(10), Some(30));
assert_eq!(Pools::api_pending_rewards(20), Some(0));
deposit_rewards(100);
assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50));
assert_eq!(Pools::api_pending_rewards(20), Some(50));
assert_eq!(Pools::api_pending_rewards(30), None);
Currency::set_balance(&30, ed + 10);
assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));
assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50));
assert_eq!(Pools::api_pending_rewards(20), Some(50));
assert_eq!(Pools::api_pending_rewards(30), Some(0));
deposit_rewards(60);
assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50 + 20));
assert_eq!(Pools::api_pending_rewards(20), Some(50 + 20));
assert_eq!(Pools::api_pending_rewards(30), Some(20));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(Pools::api_pending_rewards(10), Some(0));
assert_eq!(Pools::api_pending_rewards(20), Some(50 + 20));
assert_eq!(Pools::api_pending_rewards(30), Some(20));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_eq!(Pools::api_pending_rewards(10), Some(0));
assert_eq!(Pools::api_pending_rewards(20), Some(0));
assert_eq!(Pools::api_pending_rewards(30), Some(20));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));
assert_eq!(Pools::api_pending_rewards(10), Some(0));
assert_eq!(Pools::api_pending_rewards(20), Some(0));
assert_eq!(Pools::api_pending_rewards(30), Some(0));
});
}
#[test]
fn rewards_distribution_is_fair_bond_extra() {
ExtBuilder::default().build_and_execute(|| {
let ed = Currency::minimum_balance();
Currency::set_balance(&20, ed + 20);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
Currency::set_balance(&30, ed + 20);
assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));
deposit_rewards(40);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));
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: 20, pool_id: 1, bonded: 20, joined: true },
Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true },
Event::PaidOut { member: 10, pool_id: 1, payout: 10 },
Event::PaidOut { member: 20, pool_id: 1, payout: 20 },
Event::PaidOut { member: 30, pool_id: 1, payout: 10 }
]
);
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(30), BondExtra::FreeBalance(10)));
deposit_rewards(100);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: false },
Event::PaidOut { member: 10, pool_id: 1, payout: 20 },
Event::PaidOut { member: 20, pool_id: 1, payout: 40 },
Event::PaidOut { member: 30, pool_id: 1, payout: 40 }
]
);
});
}
#[test]
fn rewards_distribution_is_fair_unbond() {
ExtBuilder::default().build_and_execute(|| {
let ed = Currency::minimum_balance();
Currency::set_balance(&20, ed + 20);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
deposit_rewards(30);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
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: 20, pool_id: 1, bonded: 20, joined: true },
Event::PaidOut { member: 10, pool_id: 1, payout: 10 },
Event::PaidOut { member: 20, pool_id: 1, payout: 20 }
]
);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
deposit_rewards(100);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 3 },
Event::PaidOut { member: 10, pool_id: 1, payout: 50 },
Event::PaidOut { member: 20, pool_id: 1, payout: 50 },
]
);
});
}
#[test]
fn unclaimed_reward_is_safe() {
ExtBuilder::default().build_and_execute(|| {
let ed = Currency::minimum_balance();
Currency::set_balance(&20, ed + 20);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
Currency::set_balance(&30, ed + 20);
assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));
deposit_rewards(40);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
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: 20, pool_id: 1, bonded: 20, joined: true },
Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true },
Event::PaidOut { member: 10, pool_id: 1, payout: 10 },
Event::PaidOut { member: 20, pool_id: 1, payout: 20 }
]
);
deposit_rewards(80);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id: 1, payout: 20 },
Event::PaidOut { member: 20, pool_id: 1, payout: 40 }
]
);
deposit_rewards(80);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id: 1, payout: 20 },
Event::PaidOut { member: 20, pool_id: 1, payout: 40 }
]
);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 30, pool_id: 1, payout: 10 + 20 + 20 }]
);
});
}
#[test]
fn bond_extra_and_delayed_claim() {
ExtBuilder::default().build_and_execute(|| {
let ed = Currency::minimum_balance();
Currency::set_balance(&20, ed + 200);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
deposit_rewards(30);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
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: 20, pool_id: 1, bonded: 20, joined: true },
Event::PaidOut { member: 10, pool_id: 1, payout: 10 }
]
);
deposit_rewards(60);
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::FreeBalance(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 20, pool_id: 1, payout: 60 },
Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: false },
Event::PaidOut { member: 10, pool_id: 1, payout: 20 }
]
);
deposit_rewards(60);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id: 1, payout: 15 },
Event::PaidOut { member: 20, pool_id: 1, payout: 45 }
]
);
});
}
#[test]
fn create_sets_recorded_data() {
ExtBuilder::default().build_and_execute(|| {
MaxPools::<Runtime>::set(None);
let (member_10, _, reward_pool_10) = Pools::get_member_with_pools(&10).unwrap();
assert_eq!(reward_pool_10.last_recorded_total_payouts, 0);
assert_eq!(reward_pool_10.total_rewards_claimed, 0);
assert_eq!(reward_pool_10.last_recorded_reward_counter, 0.into());
assert_eq!(member_10.last_recorded_reward_counter, 0.into());
deposit_rewards(60);
Currency::set_balance(&20, 100);
assert_ok!(Pools::create(RuntimeOrigin::signed(20), 10, 20, 20, 20));
let (member_20, _, reward_pool_20) = Pools::get_member_with_pools(&20).unwrap();
assert_eq!(reward_pool_20.last_recorded_total_payouts, 0);
assert_eq!(reward_pool_20.total_rewards_claimed, 0);
assert_eq!(reward_pool_20.last_recorded_reward_counter, 0.into());
assert_eq!(member_20.last_recorded_reward_counter, 0.into());
Currency::set_balance(&Pools::create_reward_account(3), 10);
Currency::set_balance(&30, 100);
assert_ok!(Pools::create(RuntimeOrigin::signed(30), 10, 30, 30, 30));
let (member_30, _, reward_pool_30) = Pools::get_member_with_pools(&30).unwrap();
assert_eq!(
Currency::free_balance(&Pools::create_reward_account(3)),
10 + Currency::minimum_balance()
);
assert_eq!(reward_pool_30.last_recorded_total_payouts, 0);
assert_eq!(reward_pool_30.total_rewards_claimed, 0);
assert_eq!(reward_pool_30.last_recorded_reward_counter, 0.into());
assert_eq!(member_30.last_recorded_reward_counter, 0.into());
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));
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::Created { depositor: 20, pool_id: 2 },
Event::Bonded { member: 20, pool_id: 2, bonded: 10, joined: true },
Event::Created { depositor: 30, pool_id: 3 },
Event::Bonded { member: 30, pool_id: 3, bonded: 10, joined: true },
Event::PaidOut { member: 30, pool_id: 3, payout: 10 }
]
);
})
}
#[test]
fn join_updates_recorded_data() {
ExtBuilder::default().build_and_execute(|| {
MaxPoolMembers::<Runtime>::set(None);
MaxPoolMembersPerPool::<Runtime>::set(None);
let join = |x, y| {
Currency::set_balance(&x, y + Currency::minimum_balance());
assert_ok!(Pools::join(RuntimeOrigin::signed(x), y, 1));
};
{
let (member_10, _, reward_pool_10) = Pools::get_member_with_pools(&10).unwrap();
assert_eq!(reward_pool_10.last_recorded_total_payouts, 0);
assert_eq!(reward_pool_10.total_rewards_claimed, 0);
assert_eq!(reward_pool_10.last_recorded_reward_counter, 0.into());
assert_eq!(member_10.last_recorded_reward_counter, 0.into());
}
{
join(20, 10);
let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap();
assert_eq!(member.last_recorded_reward_counter, 0.into());
assert_eq!(reward_pool.last_recorded_total_payouts, 0);
assert_eq!(reward_pool.last_recorded_reward_counter, 0.into());
}
deposit_rewards(60);
{
join(30, 10);
let (member, _, reward_pool) = Pools::get_member_with_pools(&30).unwrap();
assert_eq!(reward_pool.last_recorded_total_payouts, 60);
assert_eq!(member.last_recorded_reward_counter, 3.into());
assert_eq!(reward_pool.last_recorded_reward_counter, 3.into());
}
{
join(40, 10);
let (member, _, reward_pool) = Pools::get_member_with_pools(&40).unwrap();
assert_eq!(member.last_recorded_reward_counter, 3.into());
assert_eq!(reward_pool.last_recorded_reward_counter, 3.into());
assert_eq!(reward_pool.last_recorded_total_payouts, 60);
}
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: 20, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 40, pool_id: 1, bonded: 10, joined: true }
]
);
})
}
#[test]
fn bond_extra_updates_recorded_data() {
ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
MaxPoolMembers::<Runtime>::set(None);
MaxPoolMembersPerPool::<Runtime>::set(None);
{
let (member_10, _, reward_pool_10) = Pools::get_member_with_pools(&10).unwrap();
assert_eq!(reward_pool_10.last_recorded_total_payouts, 0);
assert_eq!(reward_pool_10.total_rewards_claimed, 0);
assert_eq!(reward_pool_10.last_recorded_reward_counter, 0.into());
assert_eq!(member_10.last_recorded_reward_counter, 0.into());
}
Currency::set_balance(&10, 100);
Currency::set_balance(&20, 100);
{
assert_ok!(Pools::bond_extra(
RuntimeOrigin::signed(10),
BondExtra::FreeBalance(10)
));
let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap();
assert_eq!(member.last_recorded_reward_counter, 0.into());
assert_eq!(reward_pool.last_recorded_total_payouts, 0);
assert_eq!(reward_pool.last_recorded_reward_counter, 0.into());
}
deposit_rewards(30);
{
assert_ok!(Pools::bond_extra(
RuntimeOrigin::signed(10),
BondExtra::FreeBalance(10)
));
let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap();
assert_eq!(member.last_recorded_reward_counter, RewardCounter::from_float(0.75));
assert_eq!(
reward_pool.last_recorded_reward_counter,
RewardCounter::from_float(0.75)
);
assert_eq!(reward_pool.last_recorded_total_payouts, 30);
}
{
assert_ok!(Pools::bond_extra(
RuntimeOrigin::signed(20),
BondExtra::FreeBalance(10)
));
let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap();
assert_eq!(member.last_recorded_reward_counter, RewardCounter::from_float(0.75));
assert_eq!(
reward_pool.last_recorded_reward_counter,
RewardCounter::from_float(0.75)
);
assert_eq!(reward_pool.last_recorded_total_payouts, 30);
}
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: 20, pool_id: 1, bonded: 20, joined: true },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false },
Event::PaidOut { member: 10, pool_id: 1, payout: 15 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false },
Event::PaidOut { member: 20, pool_id: 1, payout: 15 },
Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: false }
]
);
})
}
#[test]
fn bond_extra_pending_rewards_works() {
ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
MaxPoolMembers::<Runtime>::set(None);
MaxPoolMembersPerPool::<Runtime>::set(None);
deposit_rewards(30);
System::reset_events();
{
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap();
assert_eq!(member.last_recorded_reward_counter, 1.into());
assert_eq!(reward_pool.total_rewards_claimed, 10);
assert_eq!(reward_pool.last_recorded_total_payouts, 0);
assert_eq!(reward_pool.last_recorded_reward_counter, 0.into());
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }]
);
}
{
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::Rewards));
let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap();
assert_eq!(member.last_recorded_reward_counter, 1.into());
assert_eq!(reward_pool.total_rewards_claimed, 30);
assert_eq!(reward_pool.last_recorded_total_payouts, 30);
assert_eq!(reward_pool.last_recorded_reward_counter, 1.into());
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 20, pool_id: 1, payout: 20 },
Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: false }
]
);
}
})
}
#[test]
fn unbond_updates_recorded_data() {
ExtBuilder::default()
.add_members(vec![(20, 20), (30, 20)])
.build_and_execute(|| {
MaxPoolMembers::<Runtime>::set(None);
MaxPoolMembersPerPool::<Runtime>::set(None);
{
let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap();
assert_eq!(reward_pool.last_recorded_total_payouts, 0);
assert_eq!(reward_pool.total_rewards_claimed, 0);
assert_eq!(reward_pool.last_recorded_reward_counter, 0.into());
assert_eq!(member.last_recorded_reward_counter, 0.into());
}
{
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap();
assert_eq!(member.last_recorded_reward_counter, 0.into());
assert_eq!(reward_pool.last_recorded_total_payouts, 0);
assert_eq!(reward_pool.last_recorded_reward_counter, 0.into());
}
deposit_rewards(30);
{
assert_ok!(Pools::unbond(RuntimeOrigin::signed(30), 30, 10));
let (member, _, reward_pool) = Pools::get_member_with_pools(&30).unwrap();
assert_eq!(
member.last_recorded_reward_counter,
RewardCounter::from_float(0.75)
);
assert_eq!(reward_pool.last_recorded_total_payouts, 30);
assert_eq!(
reward_pool.last_recorded_reward_counter,
RewardCounter::from_float(0.75)
);
}
{
assert_ok!(Pools::unbond(RuntimeOrigin::signed(30), 30, 5));
let (member, _, reward_pool) = Pools::get_member_with_pools(&30).unwrap();
assert_eq!(
member.last_recorded_reward_counter,
RewardCounter::from_float(0.75)
);
assert_eq!(reward_pool.last_recorded_total_payouts, 30);
assert_eq!(
reward_pool.last_recorded_reward_counter,
RewardCounter::from_float(0.75)
);
}
{
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap();
assert_eq!(
member.last_recorded_reward_counter,
RewardCounter::from_float(0.75)
);
assert_eq!(reward_pool.last_recorded_total_payouts, 30);
assert_eq!(
reward_pool.last_recorded_reward_counter,
RewardCounter::from_float(0.75)
);
}
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
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: 20, pool_id: 1, bonded: 20, joined: true },
Event::Bonded { member: 30, pool_id: 1, bonded: 20, joined: true },
Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 3 },
Event::PaidOut { member: 30, pool_id: 1, payout: 15 },
Event::Unbonded { member: 30, pool_id: 1, balance: 10, points: 10, era: 3 },
Event::Unbonded { member: 30, pool_id: 1, balance: 5, points: 5, era: 3 },
Event::PaidOut { member: 20, pool_id: 1, payout: 7 },
Event::Unbonded { member: 20, pool_id: 1, balance: 5, points: 5, era: 3 },
Event::PaidOut { member: 10, pool_id: 1, payout: 7 }
]
);
})
}
#[test]
fn rewards_are_rounded_down_depositor_collects_them() {
ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
assert_eq!(Currency::free_balance(&10), 35);
assert_eq!(
Currency::free_balance(&default_reward_account()),
Currency::minimum_balance()
);
deposit_rewards(40);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
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: 20, pool_id: 1, bonded: 20, joined: true },
Event::PaidOut { member: 10, pool_id: 1, payout: 13 },
Event::PaidOut { member: 20, pool_id: 1, payout: 26 }
]
);
assert_ok!(Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Destroying));
assert_ok!(fully_unbond_permissioned(20));
CurrentEra::set(3);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
assert_ok!(fully_unbond_permissioned(10));
CurrentEra::set(6);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
Event::Unbonded { member: 20, pool_id: 1, balance: 20, points: 20, era: 3 },
Event::Withdrawn { member: 20, pool_id: 1, balance: 20, points: 20 },
Event::MemberRemoved { pool_id: 1, member: 20 },
Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 6 },
Event::Withdrawn { member: 10, pool_id: 1, balance: 10, points: 10 },
Event::MemberRemoved { pool_id: 1, member: 10 },
Event::Destroyed { pool_id: 1 }
]
);
assert!(!Metadata::<T>::contains_key(1));
assert_eq!(Currency::free_balance(&10), 35 + 5 + 13 + 10 + 1);
})
}
#[test]
fn claim_payout_large_numbers() {
let unit = 10u128.pow(12); ExistentialDeposit::set(unit);
StakingMinBond::set(unit * 1000);
ExtBuilder::default()
.max_members(Some(4))
.max_members_per_pool(Some(4))
.add_members(vec![(20, 1500 * unit), (21, 2500 * unit), (22, 5000 * unit)])
.build_and_execute(|| {
assert_eq!(Currency::free_balance(&default_reward_account()), unit);
deposit_rewards(unit / 1000);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(21)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(22)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded {
member: 10,
pool_id: 1,
bonded: 1000000000000000,
joined: true
},
Event::Bonded {
member: 20,
pool_id: 1,
bonded: 1500000000000000,
joined: true
},
Event::Bonded {
member: 21,
pool_id: 1,
bonded: 2500000000000000,
joined: true
},
Event::Bonded {
member: 22,
pool_id: 1,
bonded: 5000000000000000,
joined: true
},
Event::PaidOut { member: 10, pool_id: 1, payout: 100000000 },
Event::PaidOut { member: 20, pool_id: 1, payout: 150000000 },
Event::PaidOut { member: 21, pool_id: 1, payout: 250000000 },
Event::PaidOut { member: 22, pool_id: 1, payout: 500000000 }
]
);
})
}
#[test]
fn claim_payout_other_works() {
ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
Currency::set_balance(&default_reward_account(), 8);
let claimable_reward = 8 - ExistentialDeposit::get();
assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3");
assert_eq!(Currency::free_balance(&10), 35);
assert_noop!(
Pools::claim_payout_other(RuntimeOrigin::signed(80), 10),
Error::<Runtime>::DoesNotHavePermission
);
assert_ok!(Pools::set_claim_permission(
RuntimeOrigin::signed(10),
ClaimPermission::PermissionlessWithdraw
));
assert_ok!(Pools::claim_payout_other(RuntimeOrigin::signed(80), 10));
assert_eq!(Currency::free_balance(&10), 36);
assert_eq!(Currency::free_balance(&default_reward_account()), 7);
})
}
}
mod unbond {
use super::*;
#[test]
fn member_unbond_open() {
ExtBuilder::default()
.min_join_bond(10)
.add_members(vec![(20, 20)])
.build_and_execute(|| {
assert_eq!(TotalValueLocked::<T>::get(), 30);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 15);
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 5);
assert_eq!(TotalValueLocked::<T>::get(), 30);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(20), 20, 10),
Error::<T>::MinimumBondNotMet
);
assert_eq!(ClaimPermissions::<Runtime>::get(10), ClaimPermission::Permissioned);
assert_ok!(Pools::set_claim_permission(
RuntimeOrigin::signed(20),
ClaimPermission::PermissionlessAll
));
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 15));
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 0);
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 20);
assert_eq!(
ClaimPermissions::<Runtime>::get(20),
ClaimPermission::PermissionlessAll
);
})
}
#[test]
fn member_kicked() {
ExtBuilder::default()
.min_join_bond(10)
.add_members(vec![(20, 20)])
.build_and_execute(|| {
unsafe_set_state(1, PoolState::Blocked);
let kicker = DEFAULT_ROLES.bouncer.unwrap();
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(kicker), 20, 5),
Error::<T>::PartialUnbondNotAllowedPermissionlessly
);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(kicker), 20, 15),
Error::<T>::PartialUnbondNotAllowedPermissionlessly
);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 2));
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 18);
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 2);
assert_ok!(Pools::unbond(RuntimeOrigin::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() {
ExtBuilder::default()
.min_join_bond(10)
.add_members(vec![(20, 20)])
.build_and_execute(|| {
unsafe_set_state(1, PoolState::Destroying);
let random = 123;
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(random), 20, 5),
Error::<T>::PartialUnbondNotAllowedPermissionlessly
);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(random), 20, 15),
Error::<T>::PartialUnbondNotAllowedPermissionlessly
);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 2));
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 18);
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 2);
assert_ok!(Pools::unbond(RuntimeOrigin::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() {
ExtBuilder::default().min_join_bond(10).build_and_execute(|| {
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 5));
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 15);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 5);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(10), 10, 10),
Error::<T>::MinimumBondNotMet
);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(10), 10, 15),
Error::<T>::MinimumBondNotMet
);
})
}
#[test]
fn depositor_kick() {
ExtBuilder::default().min_join_bond(10).build_and_execute(|| {
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);
unsafe_set_state(1, PoolState::Blocked);
let kicker = DEFAULT_ROLES.bouncer.unwrap();
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(kicker), 10, 5),
Error::<T>::PartialUnbondNotAllowedPermissionlessly
);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(kicker), 10, 15),
Error::<T>::PartialUnbondNotAllowedPermissionlessly
);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(kicker), 10, 20),
Error::<T>::DoesNotHavePermission
);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(10), 10, 20),
Error::<T>::MinimumBondNotMet
);
})
}
#[test]
fn depositor_unbond_destroying_permissionless() {
ExtBuilder::default().min_join_bond(10).build_and_execute(|| {
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);
unsafe_set_state(1, PoolState::Destroying);
let random = 123;
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(random), 10, 5),
Error::<T>::PartialUnbondNotAllowedPermissionlessly
);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(random), 10, 15),
Error::<T>::PartialUnbondNotAllowedPermissionlessly
);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(random), 10, 20),
Error::<T>::DoesNotHavePermission
);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 20));
})
}
#[test]
fn depositor_unbond_destroying_not_last_member() {
ExtBuilder::default()
.min_join_bond(10)
.add_members(vec![(20, 20)])
.build_and_execute(|| {
assert_ok!(Pools::bond_extra(
RuntimeOrigin::signed(10),
BondExtra::FreeBalance(10)
));
assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);
unsafe_set_state(1, PoolState::Destroying);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 5));
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 15);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 5);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(10), 10, 10),
Error::<T>::MinimumBondNotMet
);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(10), 10, 15),
Error::<T>::MinimumBondNotMet
);
})
}
#[test]
fn depositor_unbond_destroying_last_member() {
ExtBuilder::default().min_join_bond(10).build_and_execute(|| {
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);
unsafe_set_state(1, PoolState::Destroying);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 5));
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 15);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 5);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(10), 10, 10),
Error::<T>::MinimumBondNotMet
);
assert_ok!(Pools::unbond(RuntimeOrigin::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);
assert_ok!(fully_unbond_permissioned(10));
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
unbonding_pools_with_era! { 3 => UnbondPool::<Runtime> { points: 10, balance: 10 }}
);
assert_eq!(
BondedPool::<Runtime>::get(1).unwrap(),
BondedPool {
id: 1,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 1,
points: 0,
roles: DEFAULT_ROLES,
state: PoolState::Destroying,
}
}
);
assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0);
});
}
#[test]
fn unbond_of_3_works() {
ExtBuilder::default()
.add_members(vec![(40, 40), (550, 550)])
.build_and_execute(|| {
let ed = Currency::minimum_balance();
StakingMock::slash_by(1, 500);
Currency::set_balance(&default_reward_account(), ed + 600);
assert_ok!(fully_unbond_permissioned(40));
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
unbonding_pools_with_era! { 3 => UnbondPool { points: 6, balance: 6 }}
);
assert_eq!(
BondedPool::<Runtime>::get(1).unwrap(),
BondedPool {
id: 1,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 3,
points: 560,
roles: DEFAULT_ROLES,
state: PoolState::Open,
}
}
);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true },
Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true },
Event::PoolSlashed { pool_id: 1, balance: 100 },
Event::PaidOut { member: 40, pool_id: 1, payout: 40 },
Event::Unbonded { member: 40, pool_id: 1, balance: 6, points: 6, era: 3 }
]
);
assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 94);
assert_eq!(
PoolMembers::<Runtime>::get(40).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 6)
);
assert_eq!(Currency::free_balance(&40), 40 + 40);
unsafe_set_state(1, PoolState::Destroying);
assert_ok!(fully_unbond_permissioned(550));
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
unbonding_pools_with_era! { 3 => UnbondPool { points: 98, balance: 98 }}
);
assert_eq!(
BondedPool::<Runtime>::get(1).unwrap(),
BondedPool {
id: 1,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 3,
points: 10,
roles: DEFAULT_ROLES,
state: PoolState::Destroying,
}
}
);
assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 2);
assert_eq!(
PoolMembers::<Runtime>::get(550).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 92)
);
assert_eq!(Currency::free_balance(&550), 550 + 550);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 550, pool_id: 1, payout: 550 },
Event::Unbonded {
member: 550,
pool_id: 1,
points: 92,
balance: 92,
era: 3
}
]
);
CurrentEra::set(3);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 40, 0));
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 550, 0));
assert_ok!(fully_unbond_permissioned(10));
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
unbonding_pools_with_era! { 6 => UnbondPool { points: 2, balance: 2 }}
);
assert_eq!(
BondedPool::<Runtime>::get(1).unwrap(),
BondedPool {
id: 1,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 1,
points: 0,
roles: DEFAULT_ROLES,
state: PoolState::Destroying,
}
}
);
assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0);
assert_eq!(Currency::free_balance(&550), 550 + 550 + 92);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Withdrawn { member: 40, pool_id: 1, points: 6, balance: 6 },
Event::MemberRemoved { pool_id: 1, member: 40 },
Event::Withdrawn { member: 550, pool_id: 1, points: 92, balance: 92 },
Event::MemberRemoved { pool_id: 1, member: 550 },
Event::PaidOut { member: 10, pool_id: 1, payout: 10 },
Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2, era: 6 }
]
);
});
}
#[test]
fn unbond_merges_older_pools() {
ExtBuilder::default().with_check(1).build_and_execute(|| {
assert_eq!(StakingMock::bonding_duration(), 3);
SubPoolsStorage::<Runtime>::insert(
1,
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
3 => UnbondPool { balance: 10, points: 100 },
1 + 3 => UnbondPool { balance: 20, points: 20 },
2 + 3 => UnbondPool { balance: 101, points: 101}
},
},
);
unsafe_set_state(1, PoolState::Destroying);
let current_era = 1 + TotalUnbondingPools::<Runtime>::get();
CurrentEra::set(current_era);
assert_ok!(fully_unbond_permissioned(10));
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: UnbondPool { balance: 10 + 20, points: 100 + 20 },
with_era: unbonding_pools_with_era! {
2 + 3 => UnbondPool { balance: 101, points: 101},
current_era + 3 => UnbondPool { balance: 10, points: 10 },
},
},
);
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: 10, balance: 10, era: 9 }
]
);
});
}
#[test]
fn unbond_kick_works() {
ExtBuilder::default()
.add_members(vec![(100, 100), (200, 200)])
.build_and_execute(|| {
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);
assert_eq!(bonded_pool.roles.bouncer.unwrap(), 902);
assert_noop!(
Pools::fully_unbond(RuntimeOrigin::signed(901), 100),
Error::<Runtime>::NotKickerOrDestroying
);
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(900), 100));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true },
Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true },
Event::Unbonded {
member: 100,
pool_id: 1,
points: 100,
balance: 100,
era: 3
},
]
);
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(902), 200));
assert_eq!(
pool_events_since_last_call(),
vec![Event::Unbonded {
member: 200,
pool_id: 1,
points: 200,
balance: 200,
era: 3
}]
);
assert_eq!(
BondedPool::<Runtime>::get(1).unwrap(),
BondedPool {
id: 1,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 3,
points: 10, roles: DEFAULT_ROLES,
state: PoolState::Blocked,
}
}
);
assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 10);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
3 => UnbondPool { points: 100 + 200, balance: 100 + 200 }
},
}
);
assert_eq!(
*UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(),
vec![(3, 100), (3, 200)],
);
});
}
#[test]
fn unbond_permissionless_works() {
ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| {
unsafe_set_state(1, PoolState::Blocked);
assert_noop!(
Pools::fully_unbond(RuntimeOrigin::signed(420), 100),
Error::<Runtime>::NotKickerOrDestroying
);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(420), 100, 80),
Error::<Runtime>::PartialUnbondNotAllowedPermissionlessly,
);
unsafe_set_state(1, PoolState::Destroying);
assert_noop!(
Pools::fully_unbond(RuntimeOrigin::signed(10), 10),
Error::<Runtime>::MinimumBondNotMet,
);
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(420), 100));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true },
Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100, era: 3 }
]
);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(420), 100, 80),
Error::<Runtime>::PartialUnbondNotAllowedPermissionlessly,
);
unsafe_set_state(1, PoolState::Blocked);
assert_noop!(
Pools::fully_unbond(RuntimeOrigin::signed(420), 10),
Error::<Runtime>::DoesNotHavePermission
);
unsafe_set_state(1, PoolState::Destroying);
assert_noop!(
Pools::fully_unbond(RuntimeOrigin::signed(420), 10),
Error::<Runtime>::DoesNotHavePermission,
);
CurrentEra::set(3);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 100, 0));
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(420), 10, 5),
Error::<Runtime>::PartialUnbondNotAllowedPermissionlessly,
);
assert_noop!(
Pools::fully_unbond(RuntimeOrigin::signed(420), 10),
Error::<T>::DoesNotHavePermission
);
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(10), 10));
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 0);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
3 + 3 => UnbondPool { points: 10, balance: 10 }
}
}
);
assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0);
assert_eq!(
*UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(),
vec![(6, 10)]
);
});
}
#[test]
#[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))]
#[cfg_attr(not(debug_assertions), should_panic)]
fn unbond_errors_correctly() {
ExtBuilder::default().build_and_execute(|| {
assert_noop!(
Pools::fully_unbond(RuntimeOrigin::signed(11), 11),
Error::<Runtime>::PoolMemberNotFound
);
let member = PoolMember { pool_id: 2, points: 10, ..Default::default() };
PoolMembers::<Runtime>::insert(11, member);
let _ = Pools::fully_unbond(RuntimeOrigin::signed(11), 11);
});
}
#[test]
#[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))]
#[cfg_attr(not(debug_assertions), should_panic)]
fn unbond_panics_when_reward_pool_not_found() {
ExtBuilder::default().build_and_execute(|| {
let member = PoolMember { pool_id: 2, points: 10, ..Default::default() };
PoolMembers::<Runtime>::insert(11, member);
BondedPool::<Runtime> {
id: 1,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 1,
points: 10,
roles: DEFAULT_ROLES,
state: PoolState::Open,
},
}
.put();
let _ = Pools::fully_unbond(RuntimeOrigin::signed(11), 11);
});
}
#[test]
fn partial_unbond_era_tracking() {
ExtBuilder::default().ed(1).build_and_execute(|| {
StakingMinBond::set(1);
MinCreateBond::<T>::set(1);
MinJoinBond::<T>::set(1);
assert_eq!(Pools::depositor_min_bond(), 1);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 10);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 0);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().pool_id, 1);
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
member_unbonding_eras!()
);
assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 10);
assert!(SubPoolsStorage::<Runtime>::get(1).is_none());
assert_eq!(CurrentEra::get(), 0);
assert_eq!(BondingDuration::get(), 3);
unsafe_set_state(1, PoolState::Destroying);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 1));
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 9);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 1);
assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 9);
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 1)
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
3 => UnbondPool { points: 1, balance: 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: 1, balance: 1, era: 3 }
]
);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 5));
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 4);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 6);
assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 4);
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 6)
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
3 => UnbondPool { points: 6, balance: 6 }
}
}
);
assert_eq!(
pool_events_since_last_call(),
vec![Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5, era: 3 }]
);
CurrentEra::set(1);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 1));
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 3);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 7);
assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 3);
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 6, 4 => 1)
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
3 => UnbondPool { points: 6, balance: 6 },
4 => UnbondPool { points: 1, balance: 1 }
}
}
);
assert_eq!(
pool_events_since_last_call(),
vec![Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1, era: 4 }]
);
assert_noop!(
frame_support::storage::with_storage_layer(|| Pools::unbond(
RuntimeOrigin::signed(10),
10,
5
)),
Error::<Runtime>::MinimumBondNotMet
);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 3));
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 0);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 10);
assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 0);
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 6, 4 => 4)
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
3 => UnbondPool { points: 6, balance: 6 },
4 => UnbondPool { points: 4, balance: 4 }
}
}
);
assert_eq!(
pool_events_since_last_call(),
vec![Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3, era: 4 }]
);
});
}
#[test]
fn partial_unbond_max_chunks() {
ExtBuilder::default().add_members(vec![(20, 20)]).ed(1).build_and_execute(|| {
MaxUnbonding::set(2);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 2));
CurrentEra::set(1);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 3));
assert_eq!(
PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 2, 4 => 3)
);
CurrentEra::set(2);
assert_noop!(
frame_support::storage::with_storage_layer(|| Pools::unbond(
RuntimeOrigin::signed(20),
20,
4
)),
Error::<Runtime>::MaxUnbondingLimit
);
MaxUnbonding::set(3);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 1));
assert_eq!(
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::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
Event::Unbonded { member: 20, pool_id: 1, points: 2, balance: 2, era: 3 },
Event::Unbonded { member: 20, pool_id: 1, points: 3, balance: 3, era: 4 },
Event::Unbonded { member: 20, pool_id: 1, points: 1, balance: 1, era: 5 }
]
);
})
}
#[test]
fn depositor_permissioned_partial_unbond() {
ExtBuilder::default().ed(1).build_and_execute(|| {
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);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 3));
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 7);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 3);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(10), 10, 6),
Error::<Runtime>::MinimumBondNotMet
);
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: 3, balance: 3, era: 3 }
]
);
});
}
#[test]
fn depositor_permissioned_partial_unbond_slashed() {
ExtBuilder::default().ed(1).build_and_execute(|| {
assert_eq!(MinCreateBond::<Runtime>::get(), 2);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 10);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 0);
StakingMock::slash_by(1, 5);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(10), 10, 7),
Error::<Runtime>::MinimumBondNotMet
);
});
}
#[test]
fn every_unbonding_triggers_payout() {
ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
let initial_reward_account = Currency::free_balance(&default_reward_account());
assert_eq!(initial_reward_account, Currency::minimum_balance());
assert_eq!(initial_reward_account, 5);
Currency::set_balance(&default_reward_account(), 4 * Currency::minimum_balance());
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 2));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 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, era: 3 }
]
);
CurrentEra::set(1);
Currency::set_balance(&default_reward_account(), 4 * Currency::minimum_balance());
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 3));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 20, pool_id: 1, payout: 6 },
Event::Unbonded { member: 20, pool_id: 1, points: 3, balance: 3, era: 4 }
]
);
CurrentEra::set(2);
Currency::set_balance(&default_reward_account(), 4 * Currency::minimum_balance());
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 20, pool_id: 1, payout: 3 },
Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 5 }
]
);
assert_eq!(
PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 2, 4 => 3, 5 => 5)
);
});
}
}
mod pool_withdraw_unbonded {
use super::*;
#[test]
fn pool_withdraw_unbonded_works() {
ExtBuilder::default().add_members(vec![(20, 10)]).build_and_execute(|| {
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(20));
assert_eq!(Balances::free_balance(&default_bonded_account()), 20);
CurrentEra::set(StakingMock::current_era() + StakingMock::bonding_duration() + 1);
assert_ok!(Pools::pool_withdraw_unbonded(RuntimeOrigin::signed(10), 1, 0));
assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(15));
assert_eq!(Balances::free_balance(&default_bonded_account()), 20);
});
}
#[test]
fn pool_withdraw_unbonded_creates_tvl_diff() {
ExtBuilder::default().add_members(vec![(20, 10)]).build_and_execute(|| {
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(20));
assert_eq!(Balances::free_balance(&default_bonded_account()), 20);
assert_eq!(TotalValueLocked::<T>::get(), 20);
CurrentEra::set(StakingMock::current_era() + StakingMock::bonding_duration() + 1);
assert_ok!(Pools::pool_withdraw_unbonded(RuntimeOrigin::signed(10), 1, 0));
assert_eq!(TotalValueLocked::<T>::get(), 15);
let member_balance = PoolMembers::<T>::iter()
.map(|(_, member)| member.total_balance())
.reduce(|acc, total_balance| acc + total_balance)
.unwrap_or_default();
assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(15));
assert_eq!(Currency::free_balance(&default_bonded_account()), 20);
let non_locked_balance = Balances::free_balance(&default_bonded_account()) -
StakingMock::total_stake(&default_bonded_account()).unwrap();
assert_eq!(member_balance, TotalValueLocked::<T>::get() + non_locked_balance);
});
}
}
mod withdraw_unbonded {
use super::*;
use sp_runtime::bounded_btree_map;
#[test]
fn withdraw_unbonded_works_against_slashed_no_era_sub_pool() {
ExtBuilder::default()
.add_members(vec![(40, 40), (550, 550)])
.build_and_execute(|| {
let _ = balances_events_since_last_call();
assert_eq!(StakingMock::bonding_duration(), 3);
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(550), 550));
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(40), 40));
assert_eq!(Currency::free_balance(&default_bonded_account()), 600);
let mut current_era = 1;
CurrentEra::set(current_era);
let mut sub_pools = SubPoolsStorage::<Runtime>::get(1).unwrap();
let unbond_pool = sub_pools.with_era.get_mut(&3).unwrap();
assert_eq!(*unbond_pool, UnbondPool { points: 550 + 40, balance: 550 + 40 });
assert_eq!(TotalValueLocked::<Runtime>::get(), 600);
{
unbond_pool.balance /= 2; SubPoolsStorage::<Runtime>::insert(1, sub_pools);
TotalValueLocked::<Runtime>::mutate(|x| *x -= 295);
let mut x = UnbondingBalanceMap::get();
x.get_mut(&default_bonded_account())
.unwrap()
.get_mut(current_era as usize)
.unwrap()
.1 /= 2;
UnbondingBalanceMap::set(&x);
Currency::set_balance(
&default_bonded_account(),
Currency::free_balance(&default_bonded_account()) / 2, );
assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 10);
StakingMock::slash_by(1, 5);
assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 5);
};
current_era += TotalUnbondingPools::<Runtime>::get();
CurrentEra::set(current_era);
let sub_pools =
SubPoolsStorage::<Runtime>::get(1).unwrap().maybe_merge_pools(current_era);
SubPoolsStorage::<Runtime>::insert(1, sub_pools);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: UnbondPool { points: 550 + 40, balance: 275 + 20 },
with_era: Default::default()
}
);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true },
Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true },
Event::Unbonded {
member: 550,
pool_id: 1,
points: 550,
balance: 550,
era: 3
},
Event::Unbonded { member: 40, pool_id: 1, points: 40, balance: 40, era: 3 },
Event::PoolSlashed { pool_id: 1, balance: 5 }
]
);
assert_eq!(
balances_events_since_last_call(),
vec![BEvent::Burned { who: default_bonded_account(), amount: 300 }]
);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(550), 550, 0));
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap().no_era,
UnbondPool { points: 40, balance: 20 }
);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Withdrawn { member: 550, pool_id: 1, balance: 275, points: 550 },
Event::MemberRemoved { pool_id: 1, member: 550 }
]
);
assert_eq!(
balances_events_since_last_call(),
vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 }]
);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(40), 40, 0));
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap().no_era,
UnbondPool { points: 0, balance: 0 }
);
assert!(!PoolMembers::<Runtime>::contains_key(40));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 40 },
Event::MemberRemoved { pool_id: 1, member: 40 }
]
);
assert_eq!(
balances_events_since_last_call(),
vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 }]
);
unsafe_set_state(1, PoolState::Destroying);
assert_ok!(fully_unbond_permissioned(10));
current_era += 3;
CurrentEra::set(current_era);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Unbonded { member: 10, pool_id: 1, balance: 5, points: 5, era: 9 },
Event::Withdrawn { member: 10, pool_id: 1, balance: 5, points: 5 },
Event::MemberRemoved { pool_id: 1, member: 10 },
Event::Destroyed { pool_id: 1 }
]
);
assert!(!Metadata::<T>::contains_key(1));
assert_eq!(
balances_events_since_last_call(),
vec![
BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 },
BEvent::Thawed { who: default_reward_account(), amount: 5 },
BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 }
]
);
});
}
#[test]
fn withdraw_unbonded_works_against_slashed_with_era_sub_pools() {
ExtBuilder::default()
.add_members(vec![(40, 40), (550, 550)])
.build_and_execute(|| {
let _ = balances_events_since_last_call();
StakingMock::slash_by(1, 300);
Currency::set_balance(&default_bonded_account(), 300);
assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(300));
assert_ok!(fully_unbond_permissioned(40));
assert_ok!(fully_unbond_permissioned(550));
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2 + 40 / 2, balance: 550 / 2 + 40 / 2
}}
);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true },
Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true },
Event::PoolSlashed { pool_id: 1, balance: 300 },
Event::Unbonded { member: 40, pool_id: 1, balance: 20, points: 20, era: 3 },
Event::Unbonded {
member: 550,
pool_id: 1,
balance: 275,
points: 275,
era: 3,
}
]
);
assert_eq!(
balances_events_since_last_call(),
vec![BEvent::Burned { who: default_bonded_account(), amount: 300 },]
);
CurrentEra::set(StakingMock::bonding_duration());
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(40), 40, 0));
assert_eq!(
balances_events_since_last_call(),
vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 },]
);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 20 },
Event::MemberRemoved { pool_id: 1, member: 40 }
]
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2, balance: 550 / 2 }}
);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(550), 550, 0));
assert_eq!(
balances_events_since_last_call(),
vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 },]
);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Withdrawn { member: 550, pool_id: 1, balance: 275, points: 275 },
Event::MemberRemoved { pool_id: 1, member: 550 }
]
);
assert!(SubPoolsStorage::<Runtime>::get(1).unwrap().with_era.is_empty());
unsafe_set_state(1, PoolState::Destroying);
assert_ok!(fully_unbond_permissioned(10));
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
unbonding_pools_with_era! { 6 => UnbondPool { points: 5, balance: 5 }}
);
CurrentEra::set(CurrentEra::get() + 3);
assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1]));
assert!(Metadata::<T>::contains_key(1));
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
assert_eq!(Currency::free_balance(&10), 10 + 35);
assert_eq!(Currency::free_balance(&default_bonded_account()), 0);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5, era: 6 },
Event::Withdrawn { member: 10, pool_id: 1, points: 5, balance: 5 },
Event::MemberRemoved { pool_id: 1, member: 10 },
Event::Destroyed { pool_id: 1 }
]
);
assert!(!Metadata::<T>::contains_key(1));
assert_eq!(
balances_events_since_last_call(),
vec![
BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 },
BEvent::Thawed { who: default_reward_account(), amount: 5 },
BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 }
]
);
});
}
#[test]
fn withdraw_unbonded_handles_faulty_sub_pool_accounting() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(Currency::minimum_balance(), 5);
assert_eq!(Currency::free_balance(&10), 35);
assert_eq!(Currency::free_balance(&default_bonded_account()), 10);
unsafe_set_state(1, PoolState::Destroying);
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(10), 10));
Currency::set_balance(&default_bonded_account(), 5);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
unbonding_pools_with_era! { 3 => UnbondPool { points: 10, balance: 10 } }
);
CurrentEra::set(3);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
assert_eq!(Currency::free_balance(&10), 10 + 35);
assert_eq!(Currency::free_balance(&default_bonded_account()), 0);
});
}
#[test]
fn withdraw_unbonded_errors_correctly() {
ExtBuilder::default().with_check(0).build_and_execute(|| {
let sub_pools = SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! { 3 => UnbondPool { points: 10, balance: 10 }},
};
SubPoolsStorage::<Runtime>::insert(1, sub_pools.clone());
assert_noop!(
Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0),
Error::<Runtime>::PoolMemberNotFound
);
let mut member = PoolMember { pool_id: 1, points: 10, ..Default::default() };
PoolMembers::<Runtime>::insert(11, member.clone());
member.unbonding_eras = member_unbonding_eras!(3 => 10);
PoolMembers::<Runtime>::insert(11, member.clone());
assert_noop!(
Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0),
Error::<Runtime>::CannotWithdrawAny
);
assert_eq!(PoolMembers::<Runtime>::get(11), Some(member));
assert_eq!(SubPoolsStorage::<Runtime>::get(1).unwrap(), sub_pools)
});
}
#[test]
fn withdraw_unbonded_kick() {
ExtBuilder::default()
.add_members(vec![(100, 100), (200, 200)])
.build_and_execute(|| {
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(100), 100));
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(200), 200));
assert_eq!(
BondedPool::<Runtime>::get(1).unwrap(),
BondedPool {
id: 1,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 3,
points: 10,
roles: DEFAULT_ROLES,
state: PoolState::Open,
}
}
);
CurrentEra::set(StakingMock::bonding_duration());
assert_noop!(
Pools::withdraw_unbonded(RuntimeOrigin::signed(902), 100, 0),
Error::<Runtime>::NotKickerOrDestroying
);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true },
Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true },
Event::Unbonded {
member: 100,
pool_id: 1,
points: 100,
balance: 100,
era: 3
},
Event::Unbonded {
member: 200,
pool_id: 1,
points: 200,
balance: 200,
era: 3
}
]
);
unsafe_set_state(1, PoolState::Blocked);
assert_noop!(
Pools::withdraw_unbonded(RuntimeOrigin::signed(901), 100, 0),
Error::<Runtime>::NotKickerOrDestroying
);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(900), 100, 0));
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(900), 200, 0));
assert_eq!(Currency::free_balance(&100), 100 + 100);
assert_eq!(Currency::free_balance(&200), 200 + 200);
assert!(!PoolMembers::<Runtime>::contains_key(100));
assert!(!PoolMembers::<Runtime>::contains_key(200));
assert_eq!(SubPoolsStorage::<Runtime>::get(1).unwrap(), Default::default());
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Withdrawn { member: 100, pool_id: 1, points: 100, balance: 100 },
Event::MemberRemoved { pool_id: 1, member: 100 },
Event::Withdrawn { member: 200, pool_id: 1, points: 200, balance: 200 },
Event::MemberRemoved { pool_id: 1, member: 200 }
]
);
});
}
#[test]
fn withdraw_unbonded_destroying_permissionless() {
ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| {
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(100), 100));
assert_eq!(
BondedPool::<Runtime>::get(1).unwrap(),
BondedPool {
id: 1,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 2,
points: 10,
roles: DEFAULT_ROLES,
state: PoolState::Open,
}
}
);
CurrentEra::set(StakingMock::bonding_duration());
assert_eq!(Currency::free_balance(&100), 100);
assert_noop!(
Pools::fully_unbond(RuntimeOrigin::signed(420), 100),
Error::<Runtime>::NotKickerOrDestroying
);
unsafe_set_state(1, PoolState::Destroying);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(420), 100, 0));
assert_eq!(SubPoolsStorage::<Runtime>::get(1).unwrap(), Default::default(),);
assert_eq!(Currency::free_balance(&100), 100 + 100);
assert!(!PoolMembers::<Runtime>::contains_key(100));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true },
Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100, era: 3 },
Event::Withdrawn { member: 100, pool_id: 1, points: 100, balance: 100 },
Event::MemberRemoved { pool_id: 1, member: 100 }
]
);
});
}
#[test]
fn partial_withdraw_unbonded_depositor() {
ExtBuilder::default().ed(1).build_and_execute(|| {
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
unsafe_set_state(1, PoolState::Destroying);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 6));
CurrentEra::set(1);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 1));
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 6, 4 => 1)
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
3 => UnbondPool { points: 6, balance: 6 },
4 => UnbondPool { points: 1, balance: 1 }
}
}
);
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, era: 3 },
Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1, era: 4 }
]
);
CurrentEra::set(2);
assert_noop!(
Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0),
Error::<Runtime>::CannotWithdrawAny
);
CurrentEra::set(3);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
member_unbonding_eras!(4 => 1)
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
4 => UnbondPool { points: 1, balance: 1 }
}
}
);
assert_eq!(
pool_events_since_last_call(),
vec![Event::Withdrawn { member: 10, pool_id: 1, points: 6, balance: 6 }]
);
CurrentEra::set(4);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
member_unbonding_eras!()
);
assert_eq!(SubPoolsStorage::<Runtime>::get(1).unwrap(), Default::default());
assert_eq!(
pool_events_since_last_call(),
vec![Event::Withdrawn { member: 10, pool_id: 1, points: 1, balance: 1 },]
);
assert_noop!(
Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0),
Error::<Runtime>::CannotWithdrawAny
);
});
}
#[test]
fn partial_withdraw_unbonded_non_depositor() {
ExtBuilder::default().add_members(vec![(11, 10)]).build_and_execute(|| {
assert_ok!(Pools::unbond(RuntimeOrigin::signed(11), 11, 6));
CurrentEra::set(1);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(11), 11, 1));
assert_eq!(
PoolMembers::<Runtime>::get(11).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 6, 4 => 1)
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
3 => UnbondPool { points: 6, balance: 6 },
4 => UnbondPool { points: 1, balance: 1 }
}
}
);
assert_eq!(PoolMembers::<Runtime>::get(11).unwrap().active_points(), 3);
assert_eq!(PoolMembers::<Runtime>::get(11).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: 11, pool_id: 1, bonded: 10, joined: true },
Event::Unbonded { member: 11, pool_id: 1, points: 6, balance: 6, era: 3 },
Event::Unbonded { member: 11, pool_id: 1, points: 1, balance: 1, era: 4 }
]
);
CurrentEra::set(2);
assert_noop!(
Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0),
Error::<Runtime>::CannotWithdrawAny
);
CurrentEra::set(3);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0));
assert_eq!(
PoolMembers::<Runtime>::get(11).unwrap().unbonding_eras,
member_unbonding_eras!(4 => 1)
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
4 => UnbondPool { points: 1, balance: 1 }
}
}
);
assert_eq!(
pool_events_since_last_call(),
vec![Event::Withdrawn { member: 11, pool_id: 1, points: 6, balance: 6 }]
);
CurrentEra::set(4);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0));
assert_eq!(
PoolMembers::<Runtime>::get(11).unwrap().unbonding_eras,
member_unbonding_eras!()
);
assert_eq!(SubPoolsStorage::<Runtime>::get(1).unwrap(), Default::default());
assert_eq!(
pool_events_since_last_call(),
vec![Event::Withdrawn { member: 11, pool_id: 1, points: 1, balance: 1 }]
);
assert_noop!(
Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0),
Error::<Runtime>::CannotWithdrawAny
);
});
}
#[test]
fn full_multi_step_withdrawing_non_depositor() {
ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| {
assert_eq!(TotalValueLocked::<T>::get(), 110);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(100), 100, 75));
assert_eq!(
PoolMembers::<Runtime>::get(100).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 75)
);
assert_eq!(TotalValueLocked::<T>::get(), 110);
CurrentEra::set(1);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(100), 100, 25));
assert_eq!(
PoolMembers::<Runtime>::get(100).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 75, 4 => 25)
);
assert_noop!(
Pools::withdraw_unbonded(RuntimeOrigin::signed(100), 100, 0),
Error::<Runtime>::CannotWithdrawAny
);
assert_eq!(TotalValueLocked::<T>::get(), 110);
CurrentEra::set(3);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(100), 100, 0));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true },
Event::Unbonded { member: 100, pool_id: 1, points: 75, balance: 75, era: 3 },
Event::Unbonded { member: 100, pool_id: 1, points: 25, balance: 25, era: 4 },
Event::Withdrawn { member: 100, pool_id: 1, points: 75, balance: 75 },
]
);
assert_eq!(
PoolMembers::<Runtime>::get(100).unwrap().unbonding_eras,
member_unbonding_eras!(4 => 25)
);
assert_eq!(TotalValueLocked::<T>::get(), 35);
CurrentEra::set(4);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(100), 100, 0));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Withdrawn { member: 100, pool_id: 1, points: 25, balance: 25 },
Event::MemberRemoved { pool_id: 1, member: 100 }
]
);
})
}
#[test]
fn out_of_sync_unbonding_chunks() {
ExtBuilder::default()
.add_members(vec![(20, 100), (30, 100)])
.build_and_execute(|| {
System::reset_events();
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
assert_ok!(Pools::unbond(RuntimeOrigin::signed(30), 30, 5));
assert_eq!(
PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 5)
);
assert_eq!(
PoolMembers::<Runtime>::get(30).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 5)
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
3 => UnbondPool { points: 10, balance: 10 }
}
}
);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 3 },
Event::Unbonded { member: 30, pool_id: 1, points: 5, balance: 5, era: 3 },
]
);
CurrentEra::set(1);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
assert_eq!(
PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 5, 4 => 5)
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
3 => UnbondPool { points: 10, balance: 10 },
4 => UnbondPool { points: 5, balance: 5 }
}
}
);
assert_eq!(
pool_events_since_last_call(),
vec![Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 4 }]
);
CurrentEra::set(2);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
assert_eq!(
PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 5, 4 => 5, 5 => 5)
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
3 => UnbondPool { points: 10, balance: 10 },
4 => UnbondPool { points: 5, balance: 5 },
5 => UnbondPool { points: 5, balance: 5 }
}
}
);
assert_eq!(
pool_events_since_last_call(),
vec![Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 5 }]
);
CurrentEra::set(5);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
assert_eq!(
PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 5, 4 => 5, 5 => 5, 8 => 5)
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: UnbondPool { points: 10, balance: 10 },
with_era: unbonding_pools_with_era! {
4 => UnbondPool { points: 5, balance: 5 },
5 => UnbondPool { points: 5, balance: 5 },
8 => UnbondPool { points: 5, balance: 5 }
}
}
);
assert_eq!(
pool_events_since_last_call(),
vec![Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 8 }]
);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
assert_eq!(
PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
member_unbonding_eras!(8 => 5)
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: UnbondPool { points: 5, balance: 5 },
with_era: unbonding_pools_with_era! {
8 => UnbondPool { points: 5, balance: 5 }
}
}
);
assert_eq!(
pool_events_since_last_call(),
vec![Event::Withdrawn { member: 20, pool_id: 1, points: 15, balance: 15 }]
);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(30), 30, 0));
assert_eq!(
PoolMembers::<Runtime>::get(30).unwrap().unbonding_eras,
member_unbonding_eras!()
);
assert_eq!(
SubPoolsStorage::<Runtime>::get(1).unwrap(),
SubPools {
no_era: Default::default(),
with_era: unbonding_pools_with_era! {
8 => UnbondPool { points: 5, balance: 5 }
}
}
);
assert_eq!(
pool_events_since_last_call(),
vec![Event::Withdrawn { member: 30, pool_id: 1, points: 5, balance: 5 }]
);
})
}
#[test]
fn full_multi_step_withdrawing_depositor() {
ExtBuilder::default().ed(1).build_and_execute(|| {
assert_eq!(Pools::depositor_min_bond(), 10);
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 7));
CurrentEra::set(1);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 3));
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
member_unbonding_eras!(3 => 7, 4 => 3)
);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(10), 10, 5),
Error::<Runtime>::MinimumBondNotMet
);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(10), 10, 10),
Error::<Runtime>::MinimumBondNotMet
);
unsafe_set_state(1, PoolState::Destroying);
assert_noop!(
Pools::unbond(RuntimeOrigin::signed(10), 10, 5),
Error::<Runtime>::MinimumBondNotMet
);
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10));
CurrentEra::set(3);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false },
Event::Unbonded { member: 10, pool_id: 1, balance: 7, points: 7, era: 3 },
Event::Unbonded { member: 10, pool_id: 1, balance: 3, points: 3, era: 4 },
Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 },
Event::Withdrawn { member: 10, pool_id: 1, balance: 7, points: 7 }
]
);
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
member_unbonding_eras!(4 => 13)
);
CurrentEra::set(4);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Withdrawn { member: 10, pool_id: 1, points: 13, balance: 13 },
Event::MemberRemoved { pool_id: 1, member: 10 },
Event::Destroyed { pool_id: 1 },
]
);
assert!(!Metadata::<T>::contains_key(1));
})
}
#[test]
fn withdraw_unbonded_removes_claim_permissions_on_leave() {
ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
CurrentEra::set(1);
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20);
assert_ok!(Pools::set_claim_permission(
RuntimeOrigin::signed(20),
ClaimPermission::PermissionlessAll
));
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 20));
assert_eq!(ClaimPermissions::<Runtime>::get(20), ClaimPermission::PermissionlessAll);
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: 20, pool_id: 1, bonded: 20, joined: true },
Event::Unbonded { member: 20, pool_id: 1, balance: 20, points: 20, era: 4 },
]
);
CurrentEra::set(5);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Withdrawn { member: 20, pool_id: 1, balance: 20, points: 20 },
Event::MemberRemoved { pool_id: 1, member: 20 }
]
);
assert_eq!(PoolMembers::<Runtime>::get(20), None);
assert_eq!(ClaimPermissions::<Runtime>::contains_key(20), false);
});
}
}
mod create {
use super::*;
use frame_support::traits::fungible::InspectFreeze;
#[test]
fn create_works() {
ExtBuilder::default().build_and_execute(|| {
let next_pool_stash = Pools::create_bonded_account(2);
let ed = Currency::minimum_balance();
assert_eq!(TotalValueLocked::<T>::get(), 10);
assert!(!BondedPools::<Runtime>::contains_key(2));
assert!(!RewardPools::<Runtime>::contains_key(2));
assert!(!PoolMembers::<Runtime>::contains_key(11));
assert_err!(StakingMock::active_stake(&next_pool_stash), "balance not found");
Currency::set_balance(&11, StakingMock::minimum_nominator_bond() + ed);
assert_ok!(Pools::create(
RuntimeOrigin::signed(11),
StakingMock::minimum_nominator_bond(),
123,
456,
789
));
assert_eq!(TotalValueLocked::<T>::get(), 10 + StakingMock::minimum_nominator_bond());
assert_eq!(Currency::free_balance(&11), 0);
assert_eq!(
PoolMembers::<Runtime>::get(11).unwrap(),
PoolMember {
pool_id: 2,
points: StakingMock::minimum_nominator_bond(),
..Default::default()
}
);
assert_eq!(
BondedPool::<Runtime>::get(2).unwrap(),
BondedPool {
id: 2,
inner: BondedPoolInner {
commission: Commission::default(),
points: StakingMock::minimum_nominator_bond(),
member_counter: 1,
roles: PoolRoles {
depositor: 11,
root: Some(123),
nominator: Some(456),
bouncer: Some(789)
},
state: PoolState::Open,
}
}
);
assert_eq!(
StakingMock::active_stake(&next_pool_stash).unwrap(),
StakingMock::minimum_nominator_bond()
);
assert_eq!(
RewardPools::<Runtime>::get(2).unwrap(),
RewardPool { ..Default::default() }
);
assert_eq!(
Currency::balance_frozen(
&FreezeReason::PoolMinBalance.into(),
&default_reward_account()
),
Currency::minimum_balance()
);
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::Created { depositor: 11, pool_id: 2 },
Event::Bonded { member: 11, pool_id: 2, bonded: 10, joined: true }
]
);
});
}
#[test]
fn create_errors_correctly() {
ExtBuilder::default().with_check(0).build_and_execute(|| {
assert_noop!(
Pools::create(RuntimeOrigin::signed(10), 420, 123, 456, 789),
Error::<Runtime>::AccountBelongsToOtherPool
);
assert_eq!(MinCreateBond::<Runtime>::get(), 2);
assert_eq!(StakingMock::minimum_nominator_bond(), 10);
assert_noop!(
Pools::create(RuntimeOrigin::signed(11), 9, 123, 456, 789),
Error::<Runtime>::MinimumBondNotMet
);
MinCreateBond::<Runtime>::put(20);
assert_noop!(
Pools::create(RuntimeOrigin::signed(11), 19, 123, 456, 789),
Error::<Runtime>::MinimumBondNotMet
);
BondedPool::<Runtime> {
id: 2,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter: 1,
points: 10,
roles: DEFAULT_ROLES,
state: PoolState::Open,
},
}
.put();
assert_eq!(MaxPools::<Runtime>::get(), Some(2));
assert_eq!(BondedPools::<Runtime>::count(), 2);
assert_noop!(
Pools::create(RuntimeOrigin::signed(11), 20, 123, 456, 789),
Error::<Runtime>::MaxPools
);
assert_eq!(PoolMembers::<Runtime>::count(), 1);
MaxPools::<Runtime>::put(3);
MaxPoolMembers::<Runtime>::put(1);
Currency::set_balance(&11, 5 + 20);
let create = RuntimeCall::Pools(Call::<Runtime>::create {
amount: 20,
root: 11,
nominator: 11,
bouncer: 11,
});
assert_noop!(
create.dispatch(RuntimeOrigin::signed(11)),
Error::<Runtime>::MaxPoolMembers
);
});
}
#[test]
fn create_with_pool_id_works() {
ExtBuilder::default().build_and_execute(|| {
let ed = Currency::minimum_balance();
Currency::set_balance(&11, StakingMock::minimum_nominator_bond() + ed);
assert_ok!(Pools::create(
RuntimeOrigin::signed(11),
StakingMock::minimum_nominator_bond(),
123,
456,
789
));
assert_eq!(Currency::free_balance(&11), 0);
assert_noop!(
Pools::create_with_pool_id(RuntimeOrigin::signed(12), 20, 234, 654, 783, 1),
Error::<Runtime>::PoolIdInUse
);
assert_noop!(
Pools::create_with_pool_id(RuntimeOrigin::signed(12), 20, 234, 654, 783, 3),
Error::<Runtime>::InvalidPoolId
);
assert_ok!(Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Destroying));
assert_ok!(fully_unbond_permissioned(10));
CurrentEra::set(3);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 10));
assert_ok!(Pools::create_with_pool_id(RuntimeOrigin::signed(10), 20, 234, 654, 783, 1));
});
}
}
#[test]
fn set_claimable_actor_works() {
ExtBuilder::default().build_and_execute(|| {
Currency::set_balance(&11, ExistentialDeposit::get() + 2);
assert!(!PoolMembers::<Runtime>::contains_key(11));
assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true },
]
);
assert_eq!(ClaimPermissions::<Runtime>::get(11), ClaimPermission::Permissioned);
assert_noop!(
Pools::set_claim_permission(
RuntimeOrigin::signed(12),
ClaimPermission::PermissionlessAll
),
Error::<T>::PoolMemberNotFound
);
assert_ok!(Pools::set_claim_permission(
RuntimeOrigin::signed(11),
ClaimPermission::PermissionlessAll
));
assert_eq!(ClaimPermissions::<Runtime>::get(11), ClaimPermission::PermissionlessAll);
});
}
mod nominate {
use super::*;
#[test]
fn nominate_works() {
ExtBuilder::default().build_and_execute(|| {
assert_noop!(
Pools::nominate(RuntimeOrigin::signed(10), 1, vec![21]),
Error::<Runtime>::NotNominator
);
assert_noop!(
Pools::nominate(RuntimeOrigin::signed(902), 1, vec![21]),
Error::<Runtime>::NotNominator
);
assert_ok!(Pools::nominate(RuntimeOrigin::signed(900), 1, vec![21]));
assert_eq!(Nominations::get().unwrap(), vec![21]);
assert_ok!(Pools::nominate(RuntimeOrigin::signed(901), 1, vec![31]));
assert_eq!(Nominations::get().unwrap(), vec![31]);
assert_noop!(
Pools::nominate(RuntimeOrigin::signed(902), 123, vec![21]),
Error::<Runtime>::PoolNotFound
);
});
}
}
mod set_state {
use super::*;
#[test]
fn set_state_works() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(BondedPool::<Runtime>::get(1).unwrap().ok_to_be_open());
assert_noop!(
Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Blocked),
Error::<Runtime>::CanNotChangeState
);
assert_noop!(
Pools::set_state(RuntimeOrigin::signed(901), 1, PoolState::Blocked),
Error::<Runtime>::CanNotChangeState
);
assert_ok!(Pools::set_state(RuntimeOrigin::signed(900), 1, PoolState::Blocked));
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::StateChanged { pool_id: 1, new_state: PoolState::Blocked }
]
);
assert_eq!(BondedPool::<Runtime>::get(1).unwrap().state, PoolState::Blocked);
assert_ok!(Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Destroying));
assert_eq!(BondedPool::<Runtime>::get(1).unwrap().state, PoolState::Destroying);
assert_noop!(
Pools::set_state(RuntimeOrigin::signed(900), 1, PoolState::Blocked),
Error::<Runtime>::CanNotChangeState
);
assert_noop!(
Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Blocked),
Error::<Runtime>::CanNotChangeState
);
unsafe_set_state(1, PoolState::Open);
StakingMock::slash_by(1, 10);
assert_ok!(Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Destroying));
assert_eq!(BondedPool::<Runtime>::get(1).unwrap().state, PoolState::Destroying);
Currency::set_balance(&default_bonded_account(), Balance::MAX / 10);
unsafe_set_state(1, PoolState::Open);
assert_ok!(Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Destroying));
assert_eq!(BondedPool::<Runtime>::get(1).unwrap().state, PoolState::Destroying);
unsafe_set_state(1, PoolState::Open);
assert_noop!(
Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Blocked),
Error::<Runtime>::CanNotChangeState
);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
Event::PoolSlashed { pool_id: 1, balance: 0 },
Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying }
]
);
});
}
}
mod set_metadata {
use super::*;
#[test]
fn set_metadata_works() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1]));
assert_eq!(Metadata::<Runtime>::get(1), vec![1, 1]);
assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(902), 1, vec![2, 2]));
assert_eq!(Metadata::<Runtime>::get(1), vec![2, 2]);
assert_noop!(
Pools::set_metadata(RuntimeOrigin::signed(10), 1, vec![3, 3]),
Error::<Runtime>::DoesNotHavePermission
);
assert_noop!(
Pools::set_metadata(RuntimeOrigin::signed(901), 1, vec![3, 3]),
Error::<Runtime>::DoesNotHavePermission
);
assert_noop!(
Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1, 1]),
Error::<Runtime>::MetadataExceedsMaxLen
);
});
}
}
mod set_configs {
use super::*;
#[test]
fn set_configs_works() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Pools::set_configs(
RuntimeOrigin::root(),
ConfigOp::Set(1 as Balance),
ConfigOp::Set(2 as Balance),
ConfigOp::Set(3u32),
ConfigOp::Set(4u32),
ConfigOp::Set(5u32),
ConfigOp::Set(Perbill::from_percent(6))
));
assert_eq!(MinJoinBond::<Runtime>::get(), 1);
assert_eq!(MinCreateBond::<Runtime>::get(), 2);
assert_eq!(MaxPools::<Runtime>::get(), Some(3));
assert_eq!(MaxPoolMembers::<Runtime>::get(), Some(4));
assert_eq!(MaxPoolMembersPerPool::<Runtime>::get(), Some(5));
assert_eq!(GlobalMaxCommission::<Runtime>::get(), Some(Perbill::from_percent(6)));
assert_storage_noop!(assert_ok!(Pools::set_configs(
RuntimeOrigin::root(),
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
)));
assert_ok!(Pools::set_configs(
RuntimeOrigin::root(),
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Remove,
));
assert_eq!(MinJoinBond::<Runtime>::get(), 0);
assert_eq!(MinCreateBond::<Runtime>::get(), 0);
assert_eq!(MaxPools::<Runtime>::get(), None);
assert_eq!(MaxPoolMembers::<Runtime>::get(), None);
assert_eq!(MaxPoolMembersPerPool::<Runtime>::get(), None);
assert_eq!(GlobalMaxCommission::<Runtime>::get(), None);
});
}
}
mod bond_extra {
use super::*;
use crate::Event;
#[test]
fn bond_extra_from_free_balance_creator() {
ExtBuilder::default().build_and_execute(|| {
Currency::set_balance(&10, 100);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10);
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 10);
assert_eq!(Currency::free_balance(&10), 100);
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
assert_eq!(Currency::free_balance(&10), 90);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 20);
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 20);
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 }
]
);
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(20)));
assert_eq!(Currency::free_balance(&10), 70);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 40);
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 40);
assert_eq!(
pool_events_since_last_call(),
vec![Event::Bonded { member: 10, pool_id: 1, bonded: 20, joined: false }]
);
})
}
#[test]
fn bond_extra_from_rewards_creator() {
ExtBuilder::default().build_and_execute(|| {
Currency::set_balance(&default_reward_account(), 7);
let claimable_reward = 7 - ExistentialDeposit::get();
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10);
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 10);
assert_eq!(Currency::free_balance(&10), 35);
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::Rewards));
assert_eq!(Currency::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);
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::PaidOut { member: 10, pool_id: 1, payout: claimable_reward },
Event::Bonded {
member: 10,
pool_id: 1,
bonded: claimable_reward,
joined: false
}
]
);
})
}
#[test]
fn bond_extra_from_rewards_joiner() {
ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
Currency::set_balance(&default_reward_account(), 8);
let claimable_reward = 8 - ExistentialDeposit::get();
assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3");
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!(Currency::free_balance(&10), 35);
assert_eq!(Currency::free_balance(&20), 20);
assert_eq!(TotalValueLocked::<T>::get(), 30);
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::Rewards));
assert_eq!(Currency::free_balance(&default_reward_account()), 7);
assert_eq!(Currency::free_balance(&10), 35);
assert_eq!(TotalValueLocked::<T>::get(), 31);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10 + 1);
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30 + 1);
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::Rewards));
assert_eq!(Currency::free_balance(&20), 20);
assert_eq!(TotalValueLocked::<T>::get(), 33);
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20 + 2);
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30 + 3);
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: 20, pool_id: 1, bonded: 20, joined: true },
Event::PaidOut { member: 10, pool_id: 1, payout: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 1, joined: false },
Event::PaidOut { member: 20, pool_id: 1, payout: 2 },
Event::Bonded { member: 20, pool_id: 1, bonded: 2, joined: false }
]
);
})
}
#[test]
fn bond_extra_other() {
ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
Currency::set_balance(&default_reward_account(), 8);
let claimable_reward = 8 - ExistentialDeposit::get();
assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3");
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!(Currency::free_balance(&10), 35);
assert_eq!(Currency::free_balance(&20), 20);
assert_noop!(
Pools::bond_extra_other(RuntimeOrigin::signed(80), 20, BondExtra::Rewards),
Error::<Runtime>::DoesNotHavePermission
);
assert_ok!(Pools::set_claim_permission(
RuntimeOrigin::signed(10),
ClaimPermission::PermissionlessAll
));
assert_ok!(Pools::bond_extra_other(RuntimeOrigin::signed(50), 10, BondExtra::Rewards));
assert_eq!(Currency::free_balance(&default_reward_account()), 7);
assert_eq!(Currency::free_balance(&10), 35);
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10 + 1);
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30 + 1);
assert_noop!(
Pools::bond_extra_other(RuntimeOrigin::signed(40), 40, BondExtra::Rewards),
Error::<Runtime>::PoolMemberNotFound
);
assert_ok!(Pools::bond_extra_other(
RuntimeOrigin::signed(20),
20,
BondExtra::FreeBalance(10)
));
assert_eq!(Currency::free_balance(&20), 12);
assert_eq!(Currency::free_balance(&default_reward_account()), 5);
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 30);
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 41);
})
}
}
mod update_roles {
use super::*;
#[test]
fn update_roles_works() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(
BondedPools::<Runtime>::get(1).unwrap().roles,
PoolRoles {
depositor: 10,
root: Some(900),
nominator: Some(901),
bouncer: Some(902)
},
);
assert_noop!(
Pools::update_roles(
RuntimeOrigin::signed(1),
2,
ConfigOp::Set(5),
ConfigOp::Set(6),
ConfigOp::Set(7)
),
Error::<Runtime>::PoolNotFound,
);
assert_noop!(
Pools::update_roles(
RuntimeOrigin::signed(1),
1,
ConfigOp::Set(5),
ConfigOp::Set(6),
ConfigOp::Set(7)
),
Error::<Runtime>::DoesNotHavePermission,
);
assert_noop!(
Pools::update_roles(
RuntimeOrigin::signed(901),
1,
ConfigOp::Set(5),
ConfigOp::Set(6),
ConfigOp::Set(7)
),
Error::<Runtime>::DoesNotHavePermission,
);
assert_noop!(
Pools::update_roles(
RuntimeOrigin::signed(902),
1,
ConfigOp::Set(5),
ConfigOp::Set(6),
ConfigOp::Set(7)
),
Error::<Runtime>::DoesNotHavePermission,
);
assert_ok!(Pools::update_roles(
RuntimeOrigin::signed(900),
1,
ConfigOp::Set(5),
ConfigOp::Set(6),
ConfigOp::Set(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::RolesUpdated { root: Some(5), bouncer: Some(7), nominator: Some(6) }
]
);
assert_eq!(
BondedPools::<Runtime>::get(1).unwrap().roles,
PoolRoles { depositor: 10, root: Some(5), nominator: Some(6), bouncer: Some(7) },
);
assert_ok!(Pools::update_roles(
RuntimeOrigin::root(),
1,
ConfigOp::Set(1),
ConfigOp::Set(2),
ConfigOp::Set(3)
));
assert_eq!(
pool_events_since_last_call(),
vec![Event::RolesUpdated { root: Some(1), bouncer: Some(3), nominator: Some(2) }]
);
assert_eq!(
BondedPools::<Runtime>::get(1).unwrap().roles,
PoolRoles { depositor: 10, root: Some(1), nominator: Some(2), bouncer: Some(3) },
);
assert_ok!(Pools::update_roles(
RuntimeOrigin::root(),
1,
ConfigOp::Set(11),
ConfigOp::Noop,
ConfigOp::Noop
));
assert_eq!(
pool_events_since_last_call(),
vec![Event::RolesUpdated { root: Some(11), bouncer: Some(3), nominator: Some(2) }]
);
assert_eq!(
BondedPools::<Runtime>::get(1).unwrap().roles,
PoolRoles { depositor: 10, root: Some(11), nominator: Some(2), bouncer: Some(3) },
);
assert_ok!(Pools::update_roles(
RuntimeOrigin::root(),
1,
ConfigOp::Set(69),
ConfigOp::Remove,
ConfigOp::Remove
));
assert_eq!(
pool_events_since_last_call(),
vec![Event::RolesUpdated { root: Some(69), bouncer: None, nominator: None }]
);
assert_eq!(
BondedPools::<Runtime>::get(1).unwrap().roles,
PoolRoles { depositor: 10, root: Some(69), nominator: None, bouncer: None },
);
})
}
}
mod reward_counter_precision {
use super::*;
const DOT: Balance = 10u128.pow(10u32);
const POLKADOT_TOTAL_ISSUANCE_GENESIS: Balance = DOT * 10u128.pow(9u32);
const fn inflation(years: u128) -> u128 {
let mut i = 0;
let mut start = POLKADOT_TOTAL_ISSUANCE_GENESIS;
while i < years {
start = start + start / 10;
i += 1
}
start
}
fn default_pool_reward_counter() -> FixedU128 {
let bonded_pool = BondedPools::<T>::get(1).unwrap();
RewardPools::<Runtime>::get(1)
.unwrap()
.current_reward_counter(1, bonded_pool.points, bonded_pool.commission.current())
.unwrap()
.0
}
fn pending_rewards(of: AccountId) -> Option<BalanceOf<T>> {
let member = PoolMembers::<T>::get(of).unwrap();
assert_eq!(member.pool_id, 1);
let rc = default_pool_reward_counter();
member.pending_rewards(rc).ok()
}
#[test]
fn smallest_claimable_reward() {
let pool_bond = inflation(50);
ExtBuilder::default().ed(DOT).min_bond(pool_bond).build_and_execute(|| {
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded {
member: 10,
pool_id: 1,
bonded: 1173908528796953165005,
joined: true,
}
]
);
let expected_smallest_reward = inflation(50) / 10u128.pow(18);
deposit_rewards(expected_smallest_reward - 1);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(pool_events_since_last_call(), vec![]);
remove_rewards(expected_smallest_reward - 1);
deposit_rewards(expected_smallest_reward + 1);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 1173 }]
);
})
}
#[test]
fn massive_reward_in_small_pool() {
let tiny_bond = 1000 * DOT;
ExtBuilder::default().ed(DOT).min_bond(tiny_bond).build_and_execute(|| {
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10000000000000, joined: true }
]
);
Currency::set_balance(&20, tiny_bond);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), tiny_bond / 2, 1));
deposit_rewards(inflation(1));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Bonded { member: 20, pool_id: 1, bonded: 5000000000000, joined: true },
Event::PaidOut { member: 10, pool_id: 1, payout: 7333333333333333333 },
Event::PaidOut { member: 20, pool_id: 1, payout: 3666666666666666666 }
]
);
})
}
#[test]
fn reward_counter_calc_wont_fail_in_normal_polkadot_future() {
let pool_bond = inflation(10) / 2;
ExtBuilder::default().ed(DOT).min_bond(pool_bond).build_and_execute(|| {
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded {
member: 10,
pool_id: 1,
bonded: 12_968_712_300_500_000_000,
joined: true,
}
]
);
let pool_total_earnings_10_years = inflation(10) - POLKADOT_TOTAL_ISSUANCE_GENESIS;
deposit_rewards(pool_total_earnings_10_years);
Currency::set_balance(&20, pool_bond * 2);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), pool_bond, 1));
assert_eq!(
pool_events_since_last_call(),
vec![Event::Bonded {
member: 20,
pool_id: 1,
bonded: 12_968_712_300_500_000_000,
joined: true
}]
);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 15937424600999999996 }]
);
Currency::set_balance(&30, 20 * DOT);
assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10 * DOT, 1));
deposit_rewards(DOT);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Bonded { member: 30, pool_id: 1, bonded: 100000000000, joined: true },
Event::PaidOut { member: 30, pool_id: 1, payout: 38 }
]
);
})
}
#[test]
fn reward_counter_update_can_fail_if_pool_is_highly_slashed() {
let pool_bond = inflation(10) / 2;
ExtBuilder::default().ed(DOT).min_bond(pool_bond).build_and_execute(|| {
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded {
member: 10,
pool_id: 1,
bonded: 12_968_712_300_500_000_000,
joined: true,
}
]
);
StakingMock::slash_by(1, pool_bond * 99 / 100);
Currency::set_balance(&20, pool_bond * 2);
assert_err!(
Pools::join(RuntimeOrigin::signed(20), pool_bond, 1),
Error::<T>::OverflowRisk
);
})
}
#[test]
fn if_small_member_waits_long_enough_they_will_earn_rewards() {
ExtBuilder::default()
.ed(DOT)
.min_bond(POLKADOT_TOTAL_ISSUANCE_GENESIS / 4)
.build_and_execute(|| {
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded {
member: 10,
pool_id: 1,
bonded: 2500000000000000000,
joined: true,
}
]
);
Currency::set_balance(&20, 20 * DOT);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10 * DOT, 1));
deposit_rewards(DOT / 1000);
assert!(pending_rewards(20).unwrap().is_zero());
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Bonded {
member: 20,
pool_id: 1,
bonded: 100000000000,
joined: true
},
Event::PaidOut { member: 10, pool_id: 1, payout: 9999997 }
]
);
deposit_rewards(DOT / 1000);
assert!(pending_rewards(20).unwrap().is_zero());
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10000000 }]
);
deposit_rewards(DOT / 1000);
assert_eq!(pending_rewards(20).unwrap(), 1);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id: 1, payout: 10000000 },
Event::PaidOut { member: 20, pool_id: 1, payout: 1 }
]
);
});
}
#[test]
fn zero_reward_claim_does_not_update_reward_counter() {
ExtBuilder::default()
.ed(DOT)
.min_bond(POLKADOT_TOTAL_ISSUANCE_GENESIS / 4)
.build_and_execute(|| {
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded {
member: 10,
pool_id: 1,
bonded: 2500000000000000000,
joined: true,
}
]
);
Currency::set_balance(&20, 20 * DOT);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10 * DOT, 1));
deposit_rewards(DOT / 1000);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Bonded {
member: 20,
pool_id: 1,
bonded: 100000000000,
joined: true
},
Event::PaidOut { member: 10, pool_id: 1, payout: 9999997 }
]
);
let current_reward_counter = default_pool_reward_counter();
assert_eq!(
PoolMembers::<T>::get(10).unwrap().last_recorded_reward_counter,
current_reward_counter
);
assert_eq!(
PoolMembers::<T>::get(20).unwrap().last_recorded_reward_counter,
Default::default()
);
});
}
}
mod commission {
use super::*;
#[test]
fn set_commission_works() {
ExtBuilder::default().build_and_execute(|| {
let pool_id = 1;
let root = 900;
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(root),
pool_id,
Some((Perbill::from_percent(50), root))
));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id },
Event::Bonded { member: 10, pool_id, bonded: 10, joined: true },
Event::PoolCommissionUpdated {
pool_id,
current: Some((Perbill::from_percent(50), root))
},
]
);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(root),
1,
Some((Perbill::from_percent(25), root))
));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PoolCommissionUpdated {
pool_id,
current: Some((Perbill::from_percent(25), root))
},]
);
let payee = 901;
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(root),
pool_id,
Some((Perbill::from_percent(25), payee))
));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(25), payee))
},]
);
deposit_rewards(80);
assert_eq!(
PoolMembers::<Runtime>::get(10).unwrap(),
PoolMember::<Runtime> { pool_id, points: 10, ..Default::default() }
);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id, payout: 60 }]
);
assert_eq!(RewardPool::<Runtime>::current_balance(pool_id), 20);
assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(root), pool_id));
assert_eq!(RewardPool::<Runtime>::current_balance(pool_id), 0);
assert_eq!(
pool_events_since_last_call(),
vec![Event::PoolCommissionClaimed { pool_id: 1, commission: 20 }]
);
assert_ok!(Pools::set_commission(RuntimeOrigin::signed(root), pool_id, None));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PoolCommissionUpdated { pool_id, current: None },]
);
deposit_rewards(100);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id, payout: 100 },]
);
assert_eq!(
RewardPools::<Runtime>::get(pool_id).unwrap(),
RewardPool {
last_recorded_reward_counter: FixedU128::from_float(6.0),
last_recorded_total_payouts: 80,
total_rewards_claimed: 160,
total_commission_pending: 0,
total_commission_claimed: 20
}
);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(root),
1,
Some((Perbill::from_percent(10), root))
));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PoolCommissionUpdated {
pool_id,
current: Some((Perbill::from_percent(10), root))
},]
);
assert_eq!(
RewardPools::<Runtime>::get(pool_id).unwrap(),
RewardPool {
last_recorded_reward_counter: FixedU128::from_float(16.0),
last_recorded_total_payouts: 180,
total_rewards_claimed: 160,
total_commission_pending: 0,
total_commission_claimed: 20
}
);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(root),
pool_id,
Some((Perbill::from_percent(0), root))
));
assert_eq!(
BondedPool::<Runtime>::get(1).unwrap().commission,
Commission {
current: None,
max: None,
change_rate: None,
throttle_from: Some(1),
claim_permission: None,
}
);
assert_eq!(
pool_events_since_last_call(),
vec![Event::PoolCommissionUpdated {
pool_id,
current: Some((Perbill::from_percent(0), root))
},]
);
assert_ok!(Pools::set_commission_max(
RuntimeOrigin::signed(root),
pool_id,
Perbill::from_percent(10)
));
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(root),
pool_id,
Some((Perbill::from_percent(10), root))
));
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(root),
pool_id,
Some((Perbill::from_percent(10), payee))
));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PoolMaxCommissionUpdated {
pool_id,
max_commission: Perbill::from_percent(10)
},
Event::PoolCommissionUpdated {
pool_id,
current: Some((Perbill::from_percent(10), root))
},
Event::PoolCommissionUpdated {
pool_id,
current: Some((Perbill::from_percent(10), payee))
}
]
);
});
}
#[test]
fn commission_reward_counter_works_one_member() {
ExtBuilder::default().build_and_execute(|| {
let pool_id = 1;
let root = 900;
let member = 10;
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(root),
1,
Some((Perbill::from_percent(10), root)),
));
deposit_rewards(40);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(RewardPool::<Runtime>::current_balance(pool_id), 4);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(root),
1,
Some((Perbill::from_percent(20), root)),
));
assert_eq!(
RewardPools::<Runtime>::get(pool_id).unwrap(),
RewardPool {
last_recorded_reward_counter: FixedU128::from_float(3.6),
last_recorded_total_payouts: 40,
total_rewards_claimed: 36,
total_commission_pending: 4,
total_commission_claimed: 0
}
);
let (current_reward_counter, _) = RewardPools::<Runtime>::get(pool_id)
.unwrap()
.current_reward_counter(
pool_id,
BondedPools::<Runtime>::get(pool_id).unwrap().points,
Perbill::from_percent(20),
)
.unwrap();
assert_eq!(
PoolMembers::<Runtime>::get(member)
.unwrap()
.pending_rewards(current_reward_counter)
.unwrap(),
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::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(10), 900))
},
Event::PaidOut { member: 10, pool_id: 1, payout: 36 },
Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(20), 900))
}
]
);
})
}
#[test]
fn set_commission_handles_errors() {
ExtBuilder::default().build_and_execute(|| {
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 },
]
);
assert_noop!(
Pools::set_commission(
RuntimeOrigin::signed(900),
9999,
Some((Perbill::from_percent(1), 900)),
),
Error::<Runtime>::PoolNotFound
);
assert_noop!(
Pools::set_commission(
RuntimeOrigin::signed(1),
1,
Some((Perbill::from_percent(5), 900)),
),
Error::<Runtime>::DoesNotHavePermission
);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(5), 900)),
));
assert_ok!(Pools::set_commission_change_rate(
RuntimeOrigin::signed(900),
1,
CommissionChangeRate { max_increase: Perbill::from_percent(1), min_delay: 2_u64 }
));
assert_eq!(
BondedPool::<Runtime>::get(1).unwrap().commission,
Commission {
current: Some((Perbill::from_percent(5), 900)),
max: None,
change_rate: Some(CommissionChangeRate {
max_increase: Perbill::from_percent(1),
min_delay: 2_u64
}),
throttle_from: Some(1_u64),
claim_permission: None,
}
);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(5), 900))
},
Event::PoolCommissionChangeRateUpdated {
pool_id: 1,
change_rate: CommissionChangeRate {
max_increase: Perbill::from_percent(1),
min_delay: 2
}
}
]
);
assert_noop!(
Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(10), 900))
),
Error::<Runtime>::CommissionChangeThrottled
);
run_blocks(2);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(6), 900))
));
assert_eq!(
BondedPool::<Runtime>::get(1).unwrap().commission,
Commission {
current: Some((Perbill::from_percent(6), 900)),
max: None,
change_rate: Some(CommissionChangeRate {
max_increase: Perbill::from_percent(1),
min_delay: 2_u64
}),
throttle_from: Some(3_u64),
claim_permission: None,
}
);
assert_eq!(
pool_events_since_last_call(),
vec![Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(6), 900))
},]
);
assert_noop!(
Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(7), 900))
),
Error::<Runtime>::CommissionChangeThrottled
);
run_blocks(2);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(7), 900)),
));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(7), 900))
},]
);
run_blocks(2);
assert_noop!(
Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(9), 900)),
),
Error::<Runtime>::CommissionChangeThrottled
);
assert_ok!(Pools::set_commission_max(
RuntimeOrigin::signed(900),
1,
Perbill::from_percent(5)
));
assert_eq!(
BondedPool::<Runtime>::get(1).unwrap().commission,
Commission {
current: Some((Perbill::from_percent(5), 900)),
max: Some(Perbill::from_percent(5)),
change_rate: Some(CommissionChangeRate {
max_increase: Perbill::from_percent(1),
min_delay: 2
}),
throttle_from: Some(7),
claim_permission: None,
}
);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(5), 900))
},
Event::PoolMaxCommissionUpdated {
pool_id: 1,
max_commission: Perbill::from_percent(5)
}
]
);
run_blocks(2);
assert_noop!(
Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(6), 900)),
),
Error::<Runtime>::CommissionExceedsMaximum
);
});
}
#[test]
fn set_commission_max_works_with_error_tests() {
ExtBuilder::default().build_and_execute(|| {
assert_noop!(
Pools::set_commission_max(
RuntimeOrigin::signed(900),
9999,
Perbill::from_percent(1)
),
Error::<Runtime>::PoolNotFound
);
assert_noop!(
Pools::set_commission_max(RuntimeOrigin::signed(1), 1, Perbill::from_percent(5)),
Error::<Runtime>::DoesNotHavePermission
);
assert_noop!(
Pools::set_commission_max(
RuntimeOrigin::signed(900),
1,
Perbill::from_percent(100)
),
Error::<Runtime>::CommissionExceedsGlobalMaximum
);
assert_ok!(Pools::set_commission_max(
RuntimeOrigin::signed(900),
1,
Perbill::from_percent(80)
));
assert_eq!(
BondedPools::<Runtime>::get(1).unwrap().commission.max,
Some(Perbill::from_percent(80))
);
assert_noop!(
Pools::set_commission_max(RuntimeOrigin::signed(900), 1, Perbill::from_percent(90)),
Error::<Runtime>::MaxCommissionRestricted
);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(75), 900))
));
assert_ok!(Pools::set_commission_max(
RuntimeOrigin::signed(900),
1,
Perbill::from_percent(50)
));
assert_eq!(
BondedPools::<Runtime>::get(1).unwrap().commission,
Commission {
current: Some((Perbill::from_percent(50), 900)),
max: Some(Perbill::from_percent(50)),
change_rate: None,
throttle_from: Some(1),
claim_permission: None,
}
);
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::PoolMaxCommissionUpdated {
pool_id: 1,
max_commission: Perbill::from_percent(80)
},
Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(75), 900))
},
Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(50), 900))
},
Event::PoolMaxCommissionUpdated {
pool_id: 1,
max_commission: Perbill::from_percent(50)
}
]
);
});
}
#[test]
fn set_commission_change_rate_works_with_errors() {
ExtBuilder::default().build_and_execute(|| {
assert_noop!(
Pools::set_commission_change_rate(
RuntimeOrigin::signed(900),
9999,
CommissionChangeRate {
max_increase: Perbill::from_percent(5),
min_delay: 1000_u64
}
),
Error::<Runtime>::PoolNotFound
);
assert_noop!(
Pools::set_commission_change_rate(
RuntimeOrigin::signed(1),
1,
CommissionChangeRate {
max_increase: Perbill::from_percent(5),
min_delay: 1000_u64
}
),
Error::<Runtime>::DoesNotHavePermission
);
assert_ok!(Pools::set_commission_change_rate(
RuntimeOrigin::signed(900),
1,
CommissionChangeRate { max_increase: Perbill::from_percent(5), min_delay: 10_u64 }
));
assert_eq!(
BondedPools::<Runtime>::get(1).unwrap().commission.change_rate,
Some(CommissionChangeRate {
max_increase: Perbill::from_percent(5),
min_delay: 10_u64
})
);
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::PoolCommissionChangeRateUpdated {
pool_id: 1,
change_rate: CommissionChangeRate {
max_increase: Perbill::from_percent(5),
min_delay: 10
}
},
]
);
assert_noop!(
Pools::set_commission_change_rate(
RuntimeOrigin::signed(900),
1,
CommissionChangeRate {
max_increase: Perbill::from_percent(5),
min_delay: 5_u64
}
),
Error::<Runtime>::CommissionChangeRateNotAllowed
);
assert_noop!(
Pools::set_commission_change_rate(
RuntimeOrigin::signed(900),
1,
CommissionChangeRate {
max_increase: Perbill::from_percent(10),
min_delay: 10_u64
}
),
Error::<Runtime>::CommissionChangeRateNotAllowed
);
assert_ok!(Pools::set_commission_change_rate(
RuntimeOrigin::signed(900),
1,
CommissionChangeRate { max_increase: Perbill::from_percent(5), min_delay: 20_u64 }
));
assert_ok!(Pools::set_commission_change_rate(
RuntimeOrigin::signed(900),
1,
CommissionChangeRate { max_increase: Perbill::from_percent(4), min_delay: 20_u64 }
));
assert_ok!(Pools::set_commission_change_rate(
RuntimeOrigin::signed(900),
1,
CommissionChangeRate { max_increase: Perbill::from_percent(3), min_delay: 30_u64 }
));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PoolCommissionChangeRateUpdated {
pool_id: 1,
change_rate: CommissionChangeRate {
max_increase: Perbill::from_percent(5),
min_delay: 20
}
},
Event::PoolCommissionChangeRateUpdated {
pool_id: 1,
change_rate: CommissionChangeRate {
max_increase: Perbill::from_percent(4),
min_delay: 20
}
},
Event::PoolCommissionChangeRateUpdated {
pool_id: 1,
change_rate: CommissionChangeRate {
max_increase: Perbill::from_percent(3),
min_delay: 30
}
}
]
);
});
}
#[test]
fn change_rate_does_not_apply_to_decreasing_commission() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(10), 900))
));
assert_ok!(Pools::set_commission_change_rate(
RuntimeOrigin::signed(900),
1,
CommissionChangeRate { max_increase: Perbill::from_percent(1), min_delay: 10_u64 }
));
assert_eq!(
BondedPools::<Runtime>::get(1).unwrap().commission.change_rate,
Some(CommissionChangeRate {
max_increase: Perbill::from_percent(1),
min_delay: 10_u64
})
);
run_blocks(10_u64);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(5), 900))
));
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(3), 900))
));
assert_noop!(
Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(8), 900))
),
Error::<Runtime>::CommissionChangeThrottled
);
assert_eq!(
BondedPools::<Runtime>::get(1).unwrap().commission,
Commission {
current: Some((Perbill::from_percent(3), 900)),
max: None,
change_rate: Some(CommissionChangeRate {
max_increase: Perbill::from_percent(1),
min_delay: 10_u64
}),
throttle_from: Some(11),
claim_permission: None,
}
);
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::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(10), 900))
},
Event::PoolCommissionChangeRateUpdated {
pool_id: 1,
change_rate: CommissionChangeRate {
max_increase: Perbill::from_percent(1),
min_delay: 10
}
},
Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(5), 900))
},
Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(3), 900))
}
]
);
});
}
#[test]
fn set_commission_max_to_zero_works() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Pools::set_commission_max(RuntimeOrigin::signed(900), 1, Zero::zero()));
assert_noop!(
Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(1), 900))
),
Error::<Runtime>::CommissionExceedsMaximum
);
})
}
#[test]
fn set_commission_change_rate_zero_max_increase_works() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Pools::set_commission_change_rate(
RuntimeOrigin::signed(900),
1,
CommissionChangeRate { max_increase: Perbill::from_percent(0), min_delay: 10_u64 }
));
assert_noop!(
Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(1), 900))
),
Error::<Runtime>::CommissionChangeThrottled
);
})
}
#[test]
fn set_commission_change_rate_zero_min_delay_works() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Pools::set_commission_change_rate(
RuntimeOrigin::signed(900),
1,
CommissionChangeRate { max_increase: Perbill::from_percent(1), min_delay: 0_u64 }
));
assert_eq!(
BondedPools::<Runtime>::get(1).unwrap().commission,
Commission {
current: None,
max: None,
change_rate: Some(CommissionChangeRate {
max_increase: Perbill::from_percent(1),
min_delay: 0
}),
throttle_from: Some(1),
claim_permission: None,
}
);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(1), 900))
));
assert_noop!(
Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(3), 900))
),
Error::<Runtime>::CommissionChangeThrottled
);
})
}
#[test]
fn set_commission_change_rate_zero_value_works() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Pools::set_commission_change_rate(
RuntimeOrigin::signed(900),
1,
CommissionChangeRate { max_increase: Perbill::from_percent(0), min_delay: 0_u64 }
));
assert_noop!(
Pools::set_commission(
RuntimeOrigin::signed(900),
1,
Some((Perbill::from_percent(1), 900))
),
Error::<Runtime>::CommissionChangeThrottled
);
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::PoolCommissionChangeRateUpdated {
pool_id: 1,
change_rate: CommissionChangeRate {
max_increase: Perbill::from_percent(0),
min_delay: 0_u64
}
}
]
);
})
}
#[test]
fn do_reward_payout_with_various_commissions() {
ExtBuilder::default().build_and_execute(|| {
GlobalMaxCommission::<Runtime>::set(None);
let pool_id = 1;
let _ = Currency::set_balance(&2, 5);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
pool_id,
Some((Perbill::from_percent(33), 2)),
));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(33), 2))
},
]
);
deposit_rewards(10);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 7 },]
);
deposit_rewards(17);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 11 },]
);
deposit_rewards(50);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 34 },]
);
deposit_rewards(10439);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PaidOut { member: 10, pool_id: 1, payout: 6994 },]
);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
pool_id,
Some((Perbill::from_percent(100), 2)),
));
deposit_rewards(200);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(100), 2))
},]
);
})
}
#[test]
fn commission_accumulates_on_multiple_rewards() {
ExtBuilder::default().build_and_execute(|| {
let pool_id = 1;
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
pool_id,
Some((Perbill::from_percent(10), 2)),
));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(10), 2))
},
]
);
deposit_rewards(100);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
pool_id,
Some((Perbill::from_percent(20), 2)),
));
assert_eq!(
pool_events_since_last_call(),
vec![Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(20), 2))
},]
);
deposit_rewards(100);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(900), pool_id));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id: 1, payout: 90 + 80 },
Event::PoolCommissionClaimed { pool_id: 1, commission: 30 }
]
);
})
}
#[test]
fn last_recorded_total_payouts_needs_commission() {
ExtBuilder::default().build_and_execute(|| {
let pool_id = 1;
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
pool_id,
Some((Perbill::from_percent(10), 2)),
));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(10), 2))
},
]
);
deposit_rewards(100);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(900), pool_id));
assert_eq!(
RewardPools::<Runtime>::get(1).unwrap().last_recorded_total_payouts,
90 + 10
);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id: 1, payout: 90 },
Event::PoolCommissionClaimed { pool_id: 1, commission: 10 }
]
);
})
}
#[test]
fn do_reward_payout_with_100_percent_commission() {
ExtBuilder::default().build_and_execute(|| {
GlobalMaxCommission::<Runtime>::set(None);
let (mut member, bonded_pool, mut reward_pool) =
Pools::get_member_with_pools(&10).unwrap();
let _ = Currency::set_balance(&2, 5);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
bonded_pool.id,
Some((Perbill::from_percent(100), 2)),
));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(100), 2))
}
]
);
deposit_rewards(10);
assert_ok!(Pools::do_reward_payout(
&10,
&mut member,
&mut BondedPool::<Runtime>::get(1).unwrap(),
&mut reward_pool
));
})
}
#[test]
fn global_max_caps_max_commission_payout() {
ExtBuilder::default().build_and_execute(|| {
let (mut member, bonded_pool, mut reward_pool) =
Pools::get_member_with_pools(&10).unwrap();
let _ = Currency::set_balance(&2, 5);
assert_noop!(
Pools::set_commission(
RuntimeOrigin::signed(900),
bonded_pool.id,
Some((Perbill::from_percent(100), 2)),
),
Error::<Runtime>::CommissionExceedsGlobalMaximum
);
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 },
]
);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
bonded_pool.id,
Some((Perbill::from_percent(90), 2)),
));
GlobalMaxCommission::<Runtime>::set(Some(Perbill::from_percent(80)));
deposit_rewards(10);
assert_ok!(Pools::do_reward_payout(
&10,
&mut member,
&mut BondedPool::<Runtime>::get(1).unwrap(),
&mut reward_pool
));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PoolCommissionUpdated {
pool_id: 1,
current: Some((Perbill::from_percent(90), 2))
},
Event::PaidOut { member: 10, pool_id: 1, payout: 2 },
]
);
})
}
#[test]
fn claim_commission_works() {
ExtBuilder::default().build_and_execute(|| {
fn deposit_rewards_and_claim_payout(caller: AccountId, points: u128) {
deposit_rewards(points);
assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(caller)));
}
let pool_id = 1;
let _ = Currency::set_balance(&900, 5);
assert_ok!(Pools::set_commission(
RuntimeOrigin::signed(900),
pool_id,
Some((Perbill::from_percent(50), 900))
));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id },
Event::Bonded { member: 10, pool_id, bonded: 10, joined: true },
Event::PoolCommissionUpdated {
pool_id,
current: Some((Perbill::from_percent(50), 900))
},
]
);
deposit_rewards_and_claim_payout(10, 100);
assert_eq!(RewardPool::<Runtime>::current_balance(pool_id), 50);
assert_noop!(
Pools::claim_commission(RuntimeOrigin::signed(900), 9999,),
Error::<Runtime>::PoolNotFound
);
assert_noop!(
Pools::claim_commission(RuntimeOrigin::signed(10), pool_id,),
Error::<Runtime>::DoesNotHavePermission
);
assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(900), pool_id));
assert_eq!(RewardPool::<Runtime>::current_balance(pool_id), 0);
assert_noop!(
Pools::claim_commission(RuntimeOrigin::signed(900), pool_id,),
Error::<Runtime>::NoPendingCommission
);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id, payout: 50 },
Event::PoolCommissionClaimed { pool_id: 1, commission: 50 }
]
);
let non_pool_member = 1001;
let _ = Currency::set_balance(&non_pool_member, 5);
deposit_rewards_and_claim_payout(10, 100);
assert_ok!(Pools::set_commission_claim_permission(
RuntimeOrigin::signed(900),
pool_id,
Some(CommissionClaimPermission::Permissionless)
));
assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(non_pool_member), pool_id));
assert_eq!(RewardPool::<Runtime>::current_balance(pool_id), 0);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id, payout: 50 },
Event::PoolCommissionClaimPermissionUpdated {
pool_id: 1,
permission: Some(CommissionClaimPermission::Permissionless)
},
Event::PoolCommissionClaimed { pool_id: 1, commission: 50 },
]
);
let designated_commission_claimer = 2001;
let _ = Currency::set_balance(&designated_commission_claimer, 5);
deposit_rewards_and_claim_payout(10, 100);
assert_ok!(Pools::set_commission_claim_permission(
RuntimeOrigin::signed(900),
pool_id,
Some(CommissionClaimPermission::Account(designated_commission_claimer))
));
assert_noop!(
Pools::claim_commission(RuntimeOrigin::signed(1001), pool_id,),
Error::<Runtime>::DoesNotHavePermission
);
assert_ok!(Pools::claim_commission(
RuntimeOrigin::signed(designated_commission_claimer),
pool_id
));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id, payout: 50 },
Event::PoolCommissionClaimPermissionUpdated {
pool_id: 1,
permission: Some(CommissionClaimPermission::Account(2001))
},
Event::PoolCommissionClaimed { pool_id: 1, commission: 50 },
]
);
deposit_rewards_and_claim_payout(10, 100);
assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(900), pool_id));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id, payout: 50 },
Event::PoolCommissionClaimed { pool_id: 1, commission: 50 },
]
);
deposit_rewards_and_claim_payout(10, 100);
assert_ok!(Pools::set_commission_claim_permission(
RuntimeOrigin::signed(900),
pool_id,
None
));
assert_noop!(
Pools::claim_commission(
RuntimeOrigin::signed(designated_commission_claimer),
pool_id,
),
Error::<Runtime>::DoesNotHavePermission
);
assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(900), pool_id));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PaidOut { member: 10, pool_id, payout: 50 },
Event::PoolCommissionClaimPermissionUpdated { pool_id: 1, permission: None },
Event::PoolCommissionClaimed { pool_id: 1, commission: 50 },
]
);
})
}
#[test]
fn set_commission_claim_permission_handles_errors() {
ExtBuilder::default().build_and_execute(|| {
let pool_id = 1;
let _ = Currency::set_balance(&900, 5);
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id },
Event::Bonded { member: 10, pool_id, bonded: 10, joined: true },
]
);
assert_noop!(
Pools::set_commission_claim_permission(
RuntimeOrigin::signed(10),
90,
Some(CommissionClaimPermission::Permissionless)
),
Error::<Runtime>::PoolNotFound
);
assert_noop!(
Pools::set_commission_claim_permission(
RuntimeOrigin::signed(10),
pool_id,
Some(CommissionClaimPermission::Permissionless)
),
Error::<Runtime>::DoesNotHavePermission
);
})
}
}
mod slash {
use super::*;
#[test]
fn slash_no_subpool_is_tracked() {
let bonded = |points, member_counter| BondedPool::<Runtime> {
id: 1,
inner: BondedPoolInner {
commission: Commission::default(),
member_counter,
points,
roles: DEFAULT_ROLES,
state: PoolState::Open,
},
};
ExtBuilder::default().with_check(0).build_and_execute(|| {
Currency::set_balance(&11, ExistentialDeposit::get() + 2);
assert!(!PoolMembers::<Runtime>::contains_key(11));
assert_eq!(TotalValueLocked::<T>::get(), 10);
assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::Created { depositor: 10, pool_id: 1 },
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true },
]
);
assert_eq!(TotalValueLocked::<T>::get(), 12);
assert_eq!(
PoolMembers::<Runtime>::get(11).unwrap(),
PoolMember::<Runtime> { pool_id: 1, points: 2, ..Default::default() }
);
assert_eq!(BondedPool::<Runtime>::get(1).unwrap(), bonded(12, 2));
StakingMock::slash_by(1, 6);
Currency::set_balance(&12, ExistentialDeposit::get() + 12);
assert!(!PoolMembers::<Runtime>::contains_key(12));
assert_ok!(Pools::join(RuntimeOrigin::signed(12), 12, 1));
assert_eq!(
pool_events_since_last_call(),
vec![
Event::PoolSlashed { pool_id: 1, balance: 6 },
Event::Bonded { member: 12, pool_id: 1, bonded: 12, joined: true }
]
);
assert_eq!(TotalValueLocked::<T>::get(), 18);
assert_eq!(
PoolMembers::<Runtime>::get(12).unwrap(),
PoolMember::<Runtime> { pool_id: 1, points: 24, ..Default::default() }
);
assert_eq!(BondedPool::<Runtime>::get(1).unwrap(), bonded(12 + 24, 3));
});
}
}