use alloc::{vec, vec::Vec};
use frame_benchmarking::v2::*;
use frame_election_provider_support::SortedListProvider;
use frame_support::{
assert_ok, ensure,
traits::{
fungible::{Inspect, Mutate, Unbalanced},
tokens::Preservation,
Get, Imbalance,
},
};
use frame_system::RawOrigin as RuntimeOrigin;
use pallet_nomination_pools::{
adapter::{Member, Pool, StakeStrategy, StakeStrategyType},
BalanceOf, BondExtra, BondedPoolInner, BondedPools, ClaimPermission, ClaimPermissions,
Commission, CommissionChangeRate, CommissionClaimPermission, ConfigOp, GlobalMaxCommission,
MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond,
Pallet as Pools, PoolId, PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage,
};
use pallet_staking::MaxNominationsOf;
use sp_runtime::{
traits::{Bounded, StaticLookup, Zero},
Perbill,
};
use sp_staking::{EraIndex, StakingUnchecked};
use pallet_nomination_pools::Call;
type CurrencyOf<T> = <T as pallet_nomination_pools::Config>::Currency;
const USER_SEED: u32 = 0;
const MAX_SPANS: u32 = 100;
pub(crate) type VoterBagsListInstance = pallet_bags_list::Instance1;
pub trait Config:
pallet_nomination_pools::Config
+ pallet_staking::Config
+ pallet_bags_list::Config<VoterBagsListInstance>
{
}
pub struct Pallet<T: Config>(Pools<T>);
fn create_funded_user_with_balance<T: pallet_nomination_pools::Config>(
string: &'static str,
n: u32,
balance: BalanceOf<T>,
) -> T::AccountId {
let user = account(string, n, USER_SEED);
T::Currency::set_balance(&user, balance);
user
}
fn create_pool_account<T: pallet_nomination_pools::Config>(
n: u32,
balance: BalanceOf<T>,
commission: Option<Perbill>,
) -> (T::AccountId, T::AccountId) {
let ed = CurrencyOf::<T>::minimum_balance();
let pool_creator: T::AccountId =
create_funded_user_with_balance::<T>("pool_creator", n, ed + balance * 2u32.into());
let pool_creator_lookup = T::Lookup::unlookup(pool_creator.clone());
Pools::<T>::create(
RuntimeOrigin::Signed(pool_creator.clone()).into(),
balance,
pool_creator_lookup.clone(),
pool_creator_lookup.clone(),
pool_creator_lookup,
)
.unwrap();
if let Some(c) = commission {
let pool_id = pallet_nomination_pools::LastPoolId::<T>::get();
Pools::<T>::set_commission(
RuntimeOrigin::Signed(pool_creator.clone()).into(),
pool_id,
Some((c, pool_creator.clone())),
)
.expect("pool just created, commission can be set by root; qed");
}
let pool_account = pallet_nomination_pools::BondedPools::<T>::iter()
.find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator)
.map(|(pool_id, _)| Pools::<T>::generate_bonded_account(pool_id))
.expect("pool_creator created a pool above");
(pool_creator, pool_account)
}
fn migrate_to_transfer_stake<T: Config>(pool_id: PoolId) {
if T::StakeAdapter::strategy_type() == StakeStrategyType::Transfer {
return;
}
let pool_acc = Pools::<T>::generate_bonded_account(pool_id);
T::StakeAdapter::remove_as_agent(Pool::from(pool_acc.clone()));
PoolMembers::<T>::iter()
.filter(|(_, member)| member.pool_id == pool_id)
.for_each(|(member_acc, member)| {
let member_balance = member.total_balance();
<T as pallet_nomination_pools::Config>::Currency::transfer(
&member_acc,
&pool_acc,
member_balance,
Preservation::Preserve,
)
.expect("member should have enough balance to transfer");
});
pallet_staking::Pallet::<T>::migrate_to_direct_staker(&pool_acc);
}
fn vote_to_balance<T: pallet_nomination_pools::Config>(
vote: u64,
) -> Result<BalanceOf<T>, &'static str> {
vote.try_into().map_err(|_| "could not convert u64 to Balance")
}
fn assert_if_delegate<T: pallet_nomination_pools::Config>(assertion: bool) {
let legacy_adapter_used = T::StakeAdapter::strategy_type() != StakeStrategyType::Delegate;
assert!(assertion ^ legacy_adapter_used);
}
#[allow(unused)]
struct ListScenario<T: pallet_nomination_pools::Config> {
origin1: T::AccountId,
creator1: T::AccountId,
dest_weight: BalanceOf<T>,
origin1_member: Option<T::AccountId>,
}
impl<T: Config> ListScenario<T> {
pub(crate) fn new(
origin_weight: BalanceOf<T>,
is_increase: bool,
) -> Result<Self, &'static str> {
ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0");
ensure!(
pallet_nomination_pools::MaxPools::<T>::get().unwrap_or(0) >= 3,
"must allow at least three pools for benchmarks"
);
CurrencyOf::<T>::set_total_issuance(Zero::zero());
let (pool_creator1, pool_origin1) =
create_pool_account::<T>(USER_SEED + 1, origin_weight, Some(Perbill::from_percent(50)));
T::StakeAdapter::nominate(
Pool::from(pool_origin1.clone()),
vec![account("random_validator", 0, USER_SEED)],
)?;
let (_, pool_origin2) =
create_pool_account::<T>(USER_SEED + 2, origin_weight, Some(Perbill::from_percent(50)));
T::StakeAdapter::nominate(
Pool::from(pool_origin2.clone()),
vec![account("random_validator", 0, USER_SEED)].clone(),
)?;
let dest_weight_as_vote = <T as pallet_staking::Config>::VoterList::score_update_worst_case(
&pool_origin1,
is_increase,
);
let dest_weight: BalanceOf<T> =
dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?;
let (_, pool_dest1) =
create_pool_account::<T>(USER_SEED + 3, dest_weight, Some(Perbill::from_percent(50)));
T::StakeAdapter::nominate(
Pool::from(pool_dest1.clone()),
vec![account("random_validator", 0, USER_SEED)],
)?;
let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
assert_eq!(vote_to_balance::<T>(weight_of(&pool_origin1)).unwrap(), origin_weight);
assert_eq!(vote_to_balance::<T>(weight_of(&pool_origin2)).unwrap(), origin_weight);
assert_eq!(vote_to_balance::<T>(weight_of(&pool_dest1)).unwrap(), dest_weight);
Ok(ListScenario {
origin1: pool_origin1,
creator1: pool_creator1,
dest_weight,
origin1_member: None,
})
}
fn add_joiner(mut self, amount: BalanceOf<T>) -> Self {
let amount = MinJoinBond::<T>::get()
.max(CurrencyOf::<T>::minimum_balance())
.max(amount);
let joiner: T::AccountId = account("joiner", USER_SEED, 0);
self.origin1_member = Some(joiner.clone());
CurrencyOf::<T>::set_balance(&joiner, amount * 2u32.into());
let original_bonded = T::StakeAdapter::active_stake(Pool::from(self.origin1.clone()));
T::StakeAdapter::unbond(Pool::from(self.origin1.clone()), amount)
.expect("the pool was created in `Self::new`.");
BondedPools::<T>::mutate(&1, |maybe_pool| {
maybe_pool.as_mut().map(|pool| pool.points -= amount)
});
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), amount, 1).unwrap();
let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
assert_eq!(vote_to_balance::<T>(weight_of(&self.origin1)).unwrap(), original_bonded);
let member = PoolMembers::<T>::get(&joiner).unwrap();
assert_eq!(member.points, amount);
assert_eq!(member.pool_id, 1);
self
}
}
#[benchmarks(
where
T: pallet_staking::Config,
pallet_staking::BalanceOf<T>: From<u128>,
BalanceOf<T>: Into<u128>,
)]
mod benchmarks {
use super::*;
#[benchmark]
fn join() {
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
let scenario = ListScenario::<T>::new(origin_weight, true).unwrap();
assert_eq!(
T::StakeAdapter::active_stake(Pool::from(scenario.origin1.clone())),
origin_weight
);
let max_additional = scenario.dest_weight - origin_weight;
let joiner_free = CurrencyOf::<T>::minimum_balance() + max_additional;
let joiner: T::AccountId = create_funded_user_with_balance::<T>("joiner", 0, joiner_free);
whitelist_account!(joiner);
#[extrinsic_call]
_(RuntimeOrigin::Signed(joiner.clone()), max_additional, 1);
assert_eq!(CurrencyOf::<T>::balance(&joiner), joiner_free - max_additional);
assert_eq!(
T::StakeAdapter::active_stake(Pool::from(scenario.origin1)),
scenario.dest_weight
);
}
#[benchmark]
fn bond_extra_transfer() {
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
let scenario = ListScenario::<T>::new(origin_weight, true).unwrap();
let extra = scenario.dest_weight - origin_weight;
#[extrinsic_call]
bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra));
assert!(
T::StakeAdapter::active_stake(Pool::from(scenario.origin1)) >= scenario.dest_weight
);
}
#[benchmark]
fn bond_extra_other() {
let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0);
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
let scenario = ListScenario::<T>::new(origin_weight, true).unwrap();
let extra = (scenario.dest_weight - origin_weight).max(CurrencyOf::<T>::minimum_balance());
let _ = Pools::<T>::set_claim_permission(
RuntimeOrigin::Signed(scenario.creator1.clone()).into(),
ClaimPermission::PermissionlessAll,
);
let reward_account1 = Pools::<T>::generate_reward_account(1);
assert!(extra >= CurrencyOf::<T>::minimum_balance());
let _ = CurrencyOf::<T>::mint_into(&reward_account1, extra);
#[extrinsic_call]
_(
RuntimeOrigin::Signed(claimer),
T::Lookup::unlookup(scenario.creator1.clone()),
BondExtra::Rewards,
);
assert!(
T::StakeAdapter::active_stake(Pool::from(scenario.origin1)) >=
scenario.dest_weight / 2u32.into()
);
}
#[benchmark]
fn claim_payout() {
let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0);
let commission = Perbill::from_percent(50);
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
let ed = CurrencyOf::<T>::minimum_balance();
let (depositor, _pool_account) =
create_pool_account::<T>(0, origin_weight, Some(commission));
let reward_account = Pools::<T>::generate_reward_account(1);
CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
let _ = Pools::<T>::set_claim_permission(
RuntimeOrigin::Signed(depositor.clone()).into(),
ClaimPermission::PermissionlessAll,
);
assert_eq!(CurrencyOf::<T>::balance(&depositor), origin_weight);
whitelist_account!(depositor);
#[extrinsic_call]
claim_payout_other(RuntimeOrigin::Signed(claimer), depositor.clone());
assert_eq!(
CurrencyOf::<T>::balance(&depositor),
origin_weight + commission * origin_weight
);
assert_eq!(CurrencyOf::<T>::balance(&reward_account), ed + commission * origin_weight);
}
#[benchmark]
fn unbond() {
let origin_weight = Pools::<T>::depositor_min_bond() * 200u32.into();
let scenario = ListScenario::<T>::new(origin_weight, false).unwrap();
let amount = origin_weight - scenario.dest_weight;
let scenario = scenario.add_joiner(amount);
let member_id = scenario.origin1_member.unwrap().clone();
let member_id_lookup = T::Lookup::unlookup(member_id.clone());
let all_points = PoolMembers::<T>::get(&member_id).unwrap().points;
whitelist_account!(member_id);
#[extrinsic_call]
_(RuntimeOrigin::Signed(member_id.clone()), member_id_lookup, all_points);
let bonded_after = T::StakeAdapter::active_stake(Pool::from(scenario.origin1));
assert!(bonded_after <= scenario.dest_weight);
let member = PoolMembers::<T>::get(&member_id).unwrap();
assert_eq!(
member.unbonding_eras.keys().cloned().collect::<Vec<_>>(),
vec![0 + T::StakeAdapter::bonding_duration()]
);
assert_eq!(member.unbonding_eras.values().cloned().collect::<Vec<_>>(), vec![all_points]);
}
#[benchmark]
fn pool_withdraw_unbonded(s: Linear<0, MAX_SPANS>) {
let min_create_bond = Pools::<T>::depositor_min_bond();
let (_depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 2u32.into());
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1).unwrap();
assert_eq!(
T::StakeAdapter::active_stake(Pool::from(pool_account.clone())),
min_create_bond + min_join_bond
);
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone())
.unwrap();
assert_eq!(
T::StakeAdapter::active_stake(Pool::from(pool_account.clone())),
min_create_bond
);
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
pallet_staking::benchmarking::add_slashing_spans::<T>(&pool_account, s);
whitelist_account!(pool_account);
#[extrinsic_call]
_(RuntimeOrigin::Signed(pool_account.clone()), 1, s);
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
assert_eq!(pallet_staking::Ledger::<T>::get(pool_account).unwrap().unlocking.len(), 0);
}
#[benchmark]
fn withdraw_unbonded_update(s: Linear<0, MAX_SPANS>) {
let min_create_bond = Pools::<T>::depositor_min_bond();
let (_depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 2u32.into());
let joiner_lookup = T::Lookup::unlookup(joiner.clone());
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1).unwrap();
assert_eq!(
T::StakeAdapter::active_stake(Pool::from(pool_account.clone())),
min_create_bond + min_join_bond
);
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
pallet_staking::CurrentEra::<T>::put(0);
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone())
.unwrap();
assert_eq!(
T::StakeAdapter::active_stake(Pool::from(pool_account.clone())),
min_create_bond
);
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
pallet_staking::benchmarking::add_slashing_spans::<T>(&pool_account, s);
whitelist_account!(joiner);
#[extrinsic_call]
withdraw_unbonded(RuntimeOrigin::Signed(joiner.clone()), joiner_lookup, s);
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond * 2u32.into());
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 0);
}
#[benchmark]
fn withdraw_unbonded_kill(s: Linear<0, MAX_SPANS>) {
let min_create_bond = Pools::<T>::depositor_min_bond();
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
BondedPools::<T>::try_mutate(&1, |maybe_bonded_pool| {
maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| {
bonded_pool.state = PoolState::Destroying;
})
})
.unwrap();
pallet_staking::CurrentEra::<T>::put(0);
let reward_account = Pools::<T>::generate_reward_account(1);
assert!(frame_system::Account::<T>::contains_key(&reward_account));
Pools::<T>::fully_unbond(
RuntimeOrigin::Signed(depositor.clone()).into(),
depositor.clone(),
)
.unwrap();
assert_eq!(T::StakeAdapter::active_stake(Pool::from(pool_account.clone())), Zero::zero());
assert_eq!(
T::StakeAdapter::total_balance(Pool::from(pool_account.clone())),
Some(min_create_bond)
);
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
assert!(pallet_staking::Ledger::<T>::contains_key(&pool_account));
assert!(BondedPools::<T>::contains_key(&1));
assert!(SubPoolsStorage::<T>::contains_key(&1));
assert!(RewardPools::<T>::contains_key(&1));
assert!(PoolMembers::<T>::contains_key(&depositor));
assert!(frame_system::Account::<T>::contains_key(&reward_account));
whitelist_account!(depositor);
#[extrinsic_call]
withdraw_unbonded(RuntimeOrigin::Signed(depositor.clone()), depositor_lookup, s);
assert!(!pallet_staking::Ledger::<T>::contains_key(&pool_account));
assert!(!BondedPools::<T>::contains_key(&1));
assert!(!SubPoolsStorage::<T>::contains_key(&1));
assert!(!RewardPools::<T>::contains_key(&1));
assert!(!PoolMembers::<T>::contains_key(&depositor));
assert!(!frame_system::Account::<T>::contains_key(&pool_account));
assert!(!frame_system::Account::<T>::contains_key(&reward_account));
assert_eq!(
CurrencyOf::<T>::balance(&depositor),
min_create_bond * 2u32.into() + CurrencyOf::<T>::minimum_balance()
);
}
#[benchmark]
fn create() {
let min_create_bond = Pools::<T>::depositor_min_bond();
let depositor: T::AccountId = account("depositor", USER_SEED, 0);
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
CurrencyOf::<T>::set_balance(
&depositor,
min_create_bond + CurrencyOf::<T>::minimum_balance() * 2u32.into(),
);
assert_eq!(RewardPools::<T>::count(), 0);
assert_eq!(BondedPools::<T>::count(), 0);
whitelist_account!(depositor);
#[extrinsic_call]
_(
RuntimeOrigin::Signed(depositor.clone()),
min_create_bond,
depositor_lookup.clone(),
depositor_lookup.clone(),
depositor_lookup,
);
assert_eq!(RewardPools::<T>::count(), 1);
assert_eq!(BondedPools::<T>::count(), 1);
let (_, new_pool) = BondedPools::<T>::iter().next().unwrap();
assert_eq!(
new_pool,
BondedPoolInner {
commission: Commission::default(),
member_counter: 1,
points: min_create_bond,
roles: PoolRoles {
depositor: depositor.clone(),
root: Some(depositor.clone()),
nominator: Some(depositor.clone()),
bouncer: Some(depositor.clone()),
},
state: PoolState::Open,
}
);
assert_eq!(
T::StakeAdapter::active_stake(Pool::from(Pools::<T>::generate_bonded_account(1))),
min_create_bond
);
}
#[benchmark]
fn nominate(n: Linear<1, { MaxNominationsOf::<T>::get() }>) {
let min_create_bond = Pools::<T>::depositor_min_bond() * 2u32.into();
let (depositor, _pool_account) = create_pool_account::<T>(0, min_create_bond, None);
let validators: Vec<_> = (0..n).map(|i| account("stash", USER_SEED, i)).collect();
whitelist_account!(depositor);
#[extrinsic_call]
_(RuntimeOrigin::Signed(depositor.clone()), 1, validators);
assert_eq!(RewardPools::<T>::count(), 1);
assert_eq!(BondedPools::<T>::count(), 1);
let (_, new_pool) = BondedPools::<T>::iter().next().unwrap();
assert_eq!(
new_pool,
BondedPoolInner {
commission: Commission::default(),
member_counter: 1,
points: min_create_bond,
roles: PoolRoles {
depositor: depositor.clone(),
root: Some(depositor.clone()),
nominator: Some(depositor.clone()),
bouncer: Some(depositor.clone()),
},
state: PoolState::Open,
}
);
assert_eq!(
T::StakeAdapter::active_stake(Pool::from(Pools::<T>::generate_bonded_account(1))),
min_create_bond
);
}
#[benchmark]
fn set_state() {
let min_create_bond = Pools::<T>::depositor_min_bond();
let _ = create_pool_account::<T>(0, min_create_bond, None);
BondedPools::<T>::mutate(&1, |maybe_pool| {
maybe_pool.as_mut().map(|pool| pool.points = min_create_bond * 10u32.into());
});
let caller = account("caller", 0, USER_SEED);
whitelist_account!(caller);
#[extrinsic_call]
_(RuntimeOrigin::Signed(caller), 1, PoolState::Destroying);
assert_eq!(BondedPools::<T>::get(1).unwrap().state, PoolState::Destroying);
}
#[benchmark]
fn set_metadata(
n: Linear<1, { <T as pallet_nomination_pools::Config>::MaxMetadataLen::get() }>,
) {
let (depositor, _pool_account) =
create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
let metadata: Vec<u8> = (0..n).map(|_| 42).collect();
whitelist_account!(depositor);
#[extrinsic_call]
_(RuntimeOrigin::Signed(depositor), 1, metadata.clone());
assert_eq!(Metadata::<T>::get(&1), metadata);
}
#[benchmark]
fn set_configs() {
#[extrinsic_call]
_(
RuntimeOrigin::Root,
ConfigOp::Set(BalanceOf::<T>::max_value()),
ConfigOp::Set(BalanceOf::<T>::max_value()),
ConfigOp::Set(u32::MAX),
ConfigOp::Set(u32::MAX),
ConfigOp::Set(u32::MAX),
ConfigOp::Set(Perbill::max_value()),
);
assert_eq!(MinJoinBond::<T>::get(), BalanceOf::<T>::max_value());
assert_eq!(MinCreateBond::<T>::get(), BalanceOf::<T>::max_value());
assert_eq!(MaxPools::<T>::get(), Some(u32::MAX));
assert_eq!(MaxPoolMembers::<T>::get(), Some(u32::MAX));
assert_eq!(MaxPoolMembersPerPool::<T>::get(), Some(u32::MAX));
assert_eq!(GlobalMaxCommission::<T>::get(), Some(Perbill::max_value()));
}
#[benchmark]
fn update_roles() {
let first_id = pallet_nomination_pools::LastPoolId::<T>::get() + 1;
let (root, _) =
create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
let random: T::AccountId =
account("but is anything really random in computers..?", 0, USER_SEED);
#[extrinsic_call]
_(
RuntimeOrigin::Signed(root.clone()),
first_id,
ConfigOp::Set(random.clone()),
ConfigOp::Set(random.clone()),
ConfigOp::Set(random.clone()),
);
assert_eq!(
pallet_nomination_pools::BondedPools::<T>::get(first_id).unwrap().roles,
pallet_nomination_pools::PoolRoles {
depositor: root,
nominator: Some(random.clone()),
bouncer: Some(random.clone()),
root: Some(random),
},
)
}
#[benchmark]
fn chill() {
let (depositor, pool_account) =
create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
let validators: Vec<_> = (0..MaxNominationsOf::<T>::get())
.map(|i| account("stash", USER_SEED, i))
.collect();
assert_ok!(T::StakeAdapter::nominate(Pool::from(pool_account.clone()), validators));
assert!(T::StakeAdapter::nominations(Pool::from(pool_account.clone())).is_some());
whitelist_account!(depositor);
#[extrinsic_call]
_(RuntimeOrigin::Signed(depositor.clone()), 1);
assert!(T::StakeAdapter::nominations(Pool::from(pool_account.clone())).is_none());
}
#[benchmark]
fn set_commission() {
let (depositor, _pool_account) =
create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
Pools::<T>::set_commission_max(
RuntimeOrigin::Signed(depositor.clone()).into(),
1u32.into(),
Perbill::from_percent(50),
)
.unwrap();
Pools::<T>::set_commission_change_rate(
RuntimeOrigin::Signed(depositor.clone()).into(),
1u32.into(),
CommissionChangeRate {
max_increase: Perbill::from_percent(20),
min_delay: 0u32.into(),
},
)
.unwrap();
Pools::<T>::set_commission_claim_permission(
RuntimeOrigin::Signed(depositor.clone()).into(),
1u32.into(),
Some(CommissionClaimPermission::Account(depositor.clone())),
)
.unwrap();
#[extrinsic_call]
_(
RuntimeOrigin::Signed(depositor.clone()),
1u32.into(),
Some((Perbill::from_percent(20), depositor.clone())),
);
assert_eq!(
BondedPools::<T>::get(1).unwrap().commission,
Commission {
current: Some((Perbill::from_percent(20), depositor.clone())),
max: Some(Perbill::from_percent(50)),
change_rate: Some(CommissionChangeRate {
max_increase: Perbill::from_percent(20),
min_delay: 0u32.into()
}),
throttle_from: Some(1u32.into()),
claim_permission: Some(CommissionClaimPermission::Account(depositor)),
}
);
}
#[benchmark]
fn set_commission_max() {
let (depositor, _pool_account) = create_pool_account::<T>(
0,
Pools::<T>::depositor_min_bond() * 2u32.into(),
Some(Perbill::from_percent(50)),
);
#[extrinsic_call]
_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Perbill::from_percent(50));
assert_eq!(
BondedPools::<T>::get(1).unwrap().commission,
Commission {
current: Some((Perbill::from_percent(50), depositor)),
max: Some(Perbill::from_percent(50)),
change_rate: None,
throttle_from: Some(0u32.into()),
claim_permission: None,
}
);
}
#[benchmark]
fn set_commission_change_rate() {
let (depositor, _pool_account) =
create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
#[extrinsic_call]
_(
RuntimeOrigin::Signed(depositor.clone()),
1u32.into(),
CommissionChangeRate {
max_increase: Perbill::from_percent(50),
min_delay: 1000u32.into(),
},
);
assert_eq!(
BondedPools::<T>::get(1).unwrap().commission,
Commission {
current: None,
max: None,
change_rate: Some(CommissionChangeRate {
max_increase: Perbill::from_percent(50),
min_delay: 1000u32.into(),
}),
throttle_from: Some(1_u32.into()),
claim_permission: None,
}
);
}
#[benchmark]
fn set_commission_claim_permission() {
let (depositor, _pool_account) =
create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
#[extrinsic_call]
_(
RuntimeOrigin::Signed(depositor.clone()),
1u32.into(),
Some(CommissionClaimPermission::Account(depositor.clone())),
);
assert_eq!(
BondedPools::<T>::get(1).unwrap().commission,
Commission {
current: None,
max: None,
change_rate: None,
throttle_from: None,
claim_permission: Some(CommissionClaimPermission::Account(depositor)),
}
);
}
#[benchmark]
fn set_claim_permission() {
let min_create_bond = Pools::<T>::depositor_min_bond();
let (_depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 4u32.into());
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1).unwrap();
assert_eq!(
T::StakeAdapter::active_stake(Pool::from(pool_account.clone())),
min_create_bond + min_join_bond
);
#[extrinsic_call]
_(RuntimeOrigin::Signed(joiner.clone()), ClaimPermission::Permissioned);
assert_eq!(ClaimPermissions::<T>::get(joiner), ClaimPermission::Permissioned);
}
#[benchmark]
fn claim_commission() {
let claimer: T::AccountId = account("claimer_member", USER_SEED + 4, 0);
let commission = Perbill::from_percent(50);
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
let ed = CurrencyOf::<T>::minimum_balance();
let (depositor, _pool_account) =
create_pool_account::<T>(0, origin_weight, Some(commission));
let reward_account = Pools::<T>::generate_reward_account(1);
CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
let _ = Pools::<T>::claim_payout(RuntimeOrigin::Signed(claimer.clone()).into());
let _ = Pools::<T>::set_commission_claim_permission(
RuntimeOrigin::Signed(depositor.clone()).into(),
1u32.into(),
Some(CommissionClaimPermission::Account(claimer)),
);
whitelist_account!(depositor);
#[extrinsic_call]
_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into());
assert_eq!(
CurrencyOf::<T>::balance(&depositor),
origin_weight + commission * origin_weight
);
assert_eq!(CurrencyOf::<T>::balance(&reward_account), ed + commission * origin_weight);
}
#[benchmark]
fn adjust_pool_deposit() {
let (depositor, _) =
create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
let _ = Pools::<T>::unfreeze_pool_deposit(&Pools::<T>::generate_reward_account(1));
assert!(&Pools::<T>::check_ed_imbalance().is_err());
whitelist_account!(depositor);
#[extrinsic_call]
_(RuntimeOrigin::Signed(depositor), 1);
assert!(&Pools::<T>::check_ed_imbalance().is_ok());
}
#[benchmark]
fn apply_slash() {
let deposit_amount =
Pools::<T>::depositor_min_bond() * T::MaxUnbonding::get().into() * 4u32.into();
let (depositor, pool_account) = create_pool_account::<T>(0, deposit_amount, None);
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount);
assert_if_delegate::<T>(
T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
Some(deposit_amount),
);
let slash_amount: u128 = deposit_amount.into() / 2;
pallet_staking::slashing::do_slash::<T>(
&pool_account,
slash_amount.into(),
&mut pallet_staking::BalanceOf::<T>::zero(),
&mut pallet_staking::NegativeImbalanceOf::<T>::zero(),
EraIndex::zero(),
);
assert_eq!(
PoolMembers::<T>::get(&depositor).unwrap().total_balance(),
deposit_amount / 2u32.into()
);
assert_if_delegate::<T>(
T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
Some(deposit_amount),
);
for i in 1..(T::MaxUnbonding::get() + 1) {
pallet_staking::CurrentEra::<T>::put(i);
assert!(Pools::<T>::unbond(
RuntimeOrigin::Signed(depositor.clone()).into(),
depositor_lookup.clone(),
Pools::<T>::depositor_min_bond()
)
.is_ok());
}
pallet_staking::CurrentEra::<T>::put(T::MaxUnbonding::get() + 2);
let slash_reporter =
create_funded_user_with_balance::<T>("slasher", 0, CurrencyOf::<T>::minimum_balance());
whitelist_account!(depositor);
#[block]
{
assert_if_delegate::<T>(
Pools::<T>::apply_slash(
RuntimeOrigin::Signed(slash_reporter.clone()).into(),
depositor_lookup.clone(),
)
.is_ok(),
);
}
assert_eq!(
PoolMembers::<T>::get(&depositor).unwrap().total_balance(),
deposit_amount / 2u32.into()
);
assert_if_delegate::<T>(
T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
Some(deposit_amount / 2u32.into()),
);
}
#[benchmark]
fn apply_slash_fail() {
let deposit_amount = Pools::<T>::depositor_min_bond() * 10u32.into();
let (_depositor, pool_account) = create_pool_account::<T>(0, deposit_amount, None);
let slash_amount: u128 = deposit_amount.into() / 2;
pallet_staking::slashing::do_slash::<T>(
&pool_account,
slash_amount.into(),
&mut pallet_staking::BalanceOf::<T>::zero(),
&mut pallet_staking::NegativeImbalanceOf::<T>::zero(),
EraIndex::zero(),
);
pallet_staking::CurrentEra::<T>::put(1);
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
let join_amount = min_join_bond * T::MaxUnbonding::get().into() * 2u32.into();
let joiner = create_funded_user_with_balance::<T>("joiner", 0, join_amount * 2u32.into());
let joiner_lookup = T::Lookup::unlookup(joiner.clone());
assert!(
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), join_amount, 1).is_ok()
);
for i in 0..T::MaxUnbonding::get() {
pallet_staking::CurrentEra::<T>::put(i + 2); assert!(Pools::<T>::unbond(
RuntimeOrigin::Signed(joiner.clone()).into(),
joiner_lookup.clone(),
min_join_bond
)
.is_ok());
}
pallet_staking::CurrentEra::<T>::put(T::MaxUnbonding::get() + 3);
whitelist_account!(joiner);
#[block]
{
assert!(Pools::<T>::apply_slash(
RuntimeOrigin::Signed(joiner.clone()).into(),
joiner_lookup.clone()
)
.is_err());
}
}
#[benchmark]
fn pool_migrate() {
let deposit_amount = Pools::<T>::depositor_min_bond() * 2u32.into();
let (depositor, pool_account) = create_pool_account::<T>(0, deposit_amount, None);
let _ = migrate_to_transfer_stake::<T>(1);
#[block]
{
assert_if_delegate::<T>(
Pools::<T>::migrate_pool_to_delegate_stake(
RuntimeOrigin::Signed(depositor.clone()).into(),
1u32.into(),
)
.is_ok(),
);
}
assert_eq!(
T::StakeAdapter::total_balance(Pool::from(pool_account.clone())),
Some(deposit_amount)
);
}
#[benchmark]
fn migrate_delegation() {
let deposit_amount = Pools::<T>::depositor_min_bond() * 2u32.into();
let (depositor, _pool_account) = create_pool_account::<T>(0, deposit_amount, None);
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
let _ = migrate_to_transfer_stake::<T>(1);
assert_if_delegate::<T>(
Pools::<T>::migrate_pool_to_delegate_stake(
RuntimeOrigin::Signed(depositor.clone()).into(),
1u32.into(),
)
.is_ok(),
);
assert!(
T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())).is_none()
);
assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount);
whitelist_account!(depositor);
#[block]
{
assert_if_delegate::<T>(
Pools::<T>::migrate_delegation(
RuntimeOrigin::Signed(depositor.clone()).into(),
depositor_lookup.clone(),
)
.is_ok(),
);
}
assert_if_delegate::<T>(
T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
Some(deposit_amount),
);
assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount);
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Runtime);
}