// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Benchmarks for the nomination pools coupled with the staking and bags list pallets. #![cfg(feature = "runtime-benchmarks")] #![cfg_attr(not(feature = "std"), no_std)] #[cfg(test)] mod mock; use frame_benchmarking::v1::{account, whitelist_account}; use frame_election_provider_support::SortedListProvider; use frame_support::{ assert_ok, ensure, traits::{ fungible::{Inspect, Mutate, Unbalanced}, Get, }, }; use frame_system::RawOrigin as RuntimeOrigin; use pallet_nomination_pools::{ BalanceOf, BondExtra, BondedPoolInner, BondedPools, ClaimPermission, ClaimPermissions, Commission, CommissionChangeRate, CommissionClaimPermission, ConfigOp, GlobalMaxCommission, MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, Pallet as Pools, PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage, }; use pallet_staking::MaxNominationsOf; use sp_runtime::{ traits::{Bounded, StaticLookup, Zero}, Perbill, }; use sp_staking::{EraIndex, StakingInterface}; use sp_std::{vec, vec::Vec}; // `frame_benchmarking::benchmarks!` macro needs this use pallet_nomination_pools::Call; type CurrencyOf = ::Currency; const USER_SEED: u32 = 0; const MAX_SPANS: u32 = 100; type VoterBagsListInstance = pallet_bags_list::Instance1; pub trait Config: pallet_nomination_pools::Config + pallet_staking::Config + pallet_bags_list::Config { } pub struct Pallet(Pools); fn create_funded_user_with_balance( string: &'static str, n: u32, balance: BalanceOf, ) -> T::AccountId { let user = account(string, n, USER_SEED); T::Currency::set_balance(&user, balance); user } // Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free // balance. fn create_pool_account( n: u32, balance: BalanceOf, commission: Option, ) -> (T::AccountId, T::AccountId) { let ed = CurrencyOf::::minimum_balance(); let pool_creator: T::AccountId = create_funded_user_with_balance::("pool_creator", n, ed + balance * 2u32.into()); let pool_creator_lookup = T::Lookup::unlookup(pool_creator.clone()); Pools::::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::::get(); Pools::::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::::iter() .find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator) .map(|(pool_id, _)| Pools::::create_bonded_account(pool_id)) .expect("pool_creator created a pool above"); (pool_creator, pool_account) } fn vote_to_balance( vote: u64, ) -> Result, &'static str> { vote.try_into().map_err(|_| "could not convert u64 to Balance") } #[allow(unused)] struct ListScenario { /// Stash/Controller that is expected to be moved. origin1: T::AccountId, creator1: T::AccountId, dest_weight: BalanceOf, origin1_member: Option, } impl ListScenario { /// An expensive scenario for bags-list implementation: /// /// - the node to be updated (r) is the head of a bag that has at least one other node. The bag /// itself will need to be read and written to update its head. The node pointed to by r.next /// will need to be read and written as it will need to have its prev pointer updated. Note /// that there are two other worst case scenarios for bag removal: 1) the node is a tail and /// 2) the node is a middle node with prev and next; all scenarios end up with the same number /// of storage reads and writes. /// /// - the destination bag has at least one node, which will need its next pointer updated. pub(crate) fn new( origin_weight: BalanceOf, is_increase: bool, ) -> Result { ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); ensure!( pallet_nomination_pools::MaxPools::::get().unwrap_or(0) >= 3, "must allow at least three pools for benchmarks" ); // Burn the entire issuance. CurrencyOf::::set_total_issuance(Zero::zero()); // Create accounts with the origin weight let (pool_creator1, pool_origin1) = create_pool_account::(USER_SEED + 1, origin_weight, Some(Perbill::from_percent(50))); T::Staking::nominate( &pool_origin1, // NOTE: these don't really need to be validators. vec![account("random_validator", 0, USER_SEED)], )?; let (_, pool_origin2) = create_pool_account::(USER_SEED + 2, origin_weight, Some(Perbill::from_percent(50))); T::Staking::nominate( &pool_origin2, vec![account("random_validator", 0, USER_SEED)].clone(), )?; // Find a destination weight that will trigger the worst case scenario let dest_weight_as_vote = ::VoterList::score_update_worst_case( &pool_origin1, is_increase, ); let dest_weight: BalanceOf = dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; // Create an account with the worst case destination weight let (_, pool_dest1) = create_pool_account::(USER_SEED + 3, dest_weight, Some(Perbill::from_percent(50))); T::Staking::nominate(&pool_dest1, vec![account("random_validator", 0, USER_SEED)])?; let weight_of = pallet_staking::Pallet::::weight_of_fn(); assert_eq!(vote_to_balance::(weight_of(&pool_origin1)).unwrap(), origin_weight); assert_eq!(vote_to_balance::(weight_of(&pool_origin2)).unwrap(), origin_weight); assert_eq!(vote_to_balance::(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) -> Self { let amount = MinJoinBond::::get() .max(CurrencyOf::::minimum_balance()) // Max `amount` with minimum thresholds for account balance and joining a pool // to ensure 1) the user can be created and 2) can join the pool .max(amount); let joiner: T::AccountId = account("joiner", USER_SEED, 0); self.origin1_member = Some(joiner.clone()); CurrencyOf::::set_balance(&joiner, amount * 2u32.into()); let original_bonded = T::Staking::active_stake(&self.origin1).unwrap(); // Unbond `amount` from the underlying pool account so when the member joins // we will maintain `current_bonded`. T::Staking::unbond(&self.origin1, amount).expect("the pool was created in `Self::new`."); // Account pool points for the unbonded balance. BondedPools::::mutate(&1, |maybe_pool| { maybe_pool.as_mut().map(|pool| pool.points -= amount) }); Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), amount, 1).unwrap(); // check that the vote weight is still the same as the original bonded let weight_of = pallet_staking::Pallet::::weight_of_fn(); assert_eq!(vote_to_balance::(weight_of(&self.origin1)).unwrap(), original_bonded); // check the member was added correctly let member = PoolMembers::::get(&joiner).unwrap(); assert_eq!(member.points, amount); assert_eq!(member.pool_id, 1); self } } frame_benchmarking::benchmarks! { join { let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); // setup the worst case list scenario. let scenario = ListScenario::::new(origin_weight, true)?; assert_eq!( T::Staking::active_stake(&scenario.origin1).unwrap(), origin_weight ); let max_additional = scenario.dest_weight - origin_weight; let joiner_free = CurrencyOf::::minimum_balance() + max_additional; let joiner: T::AccountId = create_funded_user_with_balance::("joiner", 0, joiner_free); whitelist_account!(joiner); }: _(RuntimeOrigin::Signed(joiner.clone()), max_additional, 1) verify { assert_eq!(CurrencyOf::::balance(&joiner), joiner_free - max_additional); assert_eq!( T::Staking::active_stake(&scenario.origin1).unwrap(), scenario.dest_weight ); } bond_extra_transfer { let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); let scenario = ListScenario::::new(origin_weight, true)?; let extra = scenario.dest_weight - origin_weight; // creator of the src pool will bond-extra, bumping itself to dest bag. }: bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra)) verify { assert!( T::Staking::active_stake(&scenario.origin1).unwrap() >= scenario.dest_weight ); } bond_extra_other { let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0); let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); let scenario = ListScenario::::new(origin_weight, true)?; let extra = (scenario.dest_weight - origin_weight).max(CurrencyOf::::minimum_balance()); // set claim preferences to `PermissionlessAll` to any account to bond extra on member's behalf. let _ = Pools::::set_claim_permission(RuntimeOrigin::Signed(scenario.creator1.clone()).into(), ClaimPermission::PermissionlessAll); // transfer exactly `extra` to the depositor of the src pool (1), let reward_account1 = Pools::::create_reward_account(1); assert!(extra >= CurrencyOf::::minimum_balance()); let _ = CurrencyOf::::mint_into(&reward_account1, extra); }: _(RuntimeOrigin::Signed(claimer), T::Lookup::unlookup(scenario.creator1.clone()), BondExtra::Rewards) verify { // commission of 50% deducted here. assert!( T::Staking::active_stake(&scenario.origin1).unwrap() >= scenario.dest_weight / 2u32.into() ); } claim_payout { let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0); let commission = Perbill::from_percent(50); let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); let ed = CurrencyOf::::minimum_balance(); let (depositor, pool_account) = create_pool_account::(0, origin_weight, Some(commission)); let reward_account = Pools::::create_reward_account(1); // Send funds to the reward account of the pool CurrencyOf::::set_balance(&reward_account, ed + origin_weight); // set claim preferences to `PermissionlessAll` so any account can claim rewards on member's // behalf. let _ = Pools::::set_claim_permission(RuntimeOrigin::Signed(depositor.clone()).into(), ClaimPermission::PermissionlessAll); // Sanity check assert_eq!( CurrencyOf::::balance(&depositor), origin_weight ); whitelist_account!(depositor); }:claim_payout_other(RuntimeOrigin::Signed(claimer), depositor.clone()) verify { assert_eq!( CurrencyOf::::balance(&depositor), origin_weight + commission * origin_weight ); assert_eq!( CurrencyOf::::balance(&reward_account), ed + commission * origin_weight ); } unbond { // The weight the nominator will start at. The value used here is expected to be // significantly higher than the first position in a list (e.g. the first bag threshold). let origin_weight = Pools::::depositor_min_bond() * 200u32.into(); let scenario = ListScenario::::new(origin_weight, false)?; 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::::get(&member_id).unwrap().points; whitelist_account!(member_id); }: _(RuntimeOrigin::Signed(member_id.clone()), member_id_lookup, all_points) verify { let bonded_after = T::Staking::active_stake(&scenario.origin1).unwrap(); // We at least went down to the destination bag assert!(bonded_after <= scenario.dest_weight); let member = PoolMembers::::get( &member_id ) .unwrap(); assert_eq!( member.unbonding_eras.keys().cloned().collect::>(), vec![0 + T::Staking::bonding_duration()] ); assert_eq!( member.unbonding_eras.values().cloned().collect::>(), vec![all_points] ); } pool_withdraw_unbonded { let s in 0 .. MAX_SPANS; let min_create_bond = Pools::::depositor_min_bond(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); // Add a new member let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1) .unwrap(); // Sanity check join worked assert_eq!( T::Staking::active_stake(&pool_account).unwrap(), min_create_bond + min_join_bond ); assert_eq!(CurrencyOf::::balance(&joiner), min_join_bond); // Unbond the new member Pools::::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); // Sanity check that unbond worked assert_eq!( T::Staking::active_stake(&pool_account).unwrap(), min_create_bond ); assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); // Set the current era pallet_staking::CurrentEra::::put(EraIndex::max_value()); // Add `s` count of slashing spans to storage. pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); whitelist_account!(pool_account); }: _(RuntimeOrigin::Signed(pool_account.clone()), 1, s) verify { // The joiners funds didn't change assert_eq!(CurrencyOf::::balance(&joiner), min_join_bond); // The unlocking chunk was removed assert_eq!(pallet_staking::Ledger::::get(pool_account).unwrap().unlocking.len(), 0); } withdraw_unbonded_update { let s in 0 .. MAX_SPANS; let min_create_bond = Pools::::depositor_min_bond(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); // Add a new member let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); let joiner_lookup = T::Lookup::unlookup(joiner.clone()); Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1) .unwrap(); // Sanity check join worked assert_eq!( T::Staking::active_stake(&pool_account).unwrap(), min_create_bond + min_join_bond ); assert_eq!(CurrencyOf::::balance(&joiner), min_join_bond); // Unbond the new member pallet_staking::CurrentEra::::put(0); Pools::::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); // Sanity check that unbond worked assert_eq!( T::Staking::active_stake(&pool_account).unwrap(), min_create_bond ); assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); // Set the current era to ensure we can withdraw unbonded funds pallet_staking::CurrentEra::::put(EraIndex::max_value()); pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); whitelist_account!(joiner); }: withdraw_unbonded(RuntimeOrigin::Signed(joiner.clone()), joiner_lookup, s) verify { assert_eq!( CurrencyOf::::balance(&joiner), min_join_bond * 2u32.into() ); // The unlocking chunk was removed assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 0); } withdraw_unbonded_kill { let s in 0 .. MAX_SPANS; let min_create_bond = Pools::::depositor_min_bond(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); let depositor_lookup = T::Lookup::unlookup(depositor.clone()); // We set the pool to the destroying state so the depositor can leave BondedPools::::try_mutate(&1, |maybe_bonded_pool| { maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { bonded_pool.state = PoolState::Destroying; }) }) .unwrap(); // Unbond the creator pallet_staking::CurrentEra::::put(0); // Simulate some rewards so we can check if the rewards storage is cleaned up. We check this // here to ensure the complete flow for destroying a pool works - the reward pool account // should never exist by time the depositor withdraws so we test that it gets cleaned // up when unbonding. let reward_account = Pools::::create_reward_account(1); assert!(frame_system::Account::::contains_key(&reward_account)); Pools::::fully_unbond(RuntimeOrigin::Signed(depositor.clone()).into(), depositor.clone()).unwrap(); // Sanity check that unbond worked assert_eq!( T::Staking::active_stake(&pool_account).unwrap(), Zero::zero() ); assert_eq!( CurrencyOf::::balance(&pool_account), min_create_bond ); assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); // Set the current era to ensure we can withdraw unbonded funds pallet_staking::CurrentEra::::put(EraIndex::max_value()); // Some last checks that storage items we expect to get cleaned up are present assert!(pallet_staking::Ledger::::contains_key(&pool_account)); assert!(BondedPools::::contains_key(&1)); assert!(SubPoolsStorage::::contains_key(&1)); assert!(RewardPools::::contains_key(&1)); assert!(PoolMembers::::contains_key(&depositor)); assert!(frame_system::Account::::contains_key(&reward_account)); whitelist_account!(depositor); }: withdraw_unbonded(RuntimeOrigin::Signed(depositor.clone()), depositor_lookup, s) verify { // Pool removal worked assert!(!pallet_staking::Ledger::::contains_key(&pool_account)); assert!(!BondedPools::::contains_key(&1)); assert!(!SubPoolsStorage::::contains_key(&1)); assert!(!RewardPools::::contains_key(&1)); assert!(!PoolMembers::::contains_key(&depositor)); assert!(!frame_system::Account::::contains_key(&pool_account)); assert!(!frame_system::Account::::contains_key(&reward_account)); // Funds where transferred back correctly assert_eq!( CurrencyOf::::balance(&depositor), // gets bond back + rewards collecting when unbonding min_create_bond * 2u32.into() + CurrencyOf::::minimum_balance() ); } create { let min_create_bond = Pools::::depositor_min_bond(); let depositor: T::AccountId = account("depositor", USER_SEED, 0); let depositor_lookup = T::Lookup::unlookup(depositor.clone()); // Give the depositor some balance to bond CurrencyOf::::set_balance(&depositor, min_create_bond * 2u32.into()); // Make sure no Pools exist at a pre-condition for our verify checks assert_eq!(RewardPools::::count(), 0); assert_eq!(BondedPools::::count(), 0); whitelist_account!(depositor); }: _( RuntimeOrigin::Signed(depositor.clone()), min_create_bond, depositor_lookup.clone(), depositor_lookup.clone(), depositor_lookup ) verify { assert_eq!(RewardPools::::count(), 1); assert_eq!(BondedPools::::count(), 1); let (_, new_pool) = BondedPools::::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::Staking::active_stake(&Pools::::create_bonded_account(1)), Ok(min_create_bond) ); } nominate { let n in 1 .. MaxNominationsOf::::get(); // Create a pool let min_create_bond = Pools::::depositor_min_bond() * 2u32.into(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); // Create some accounts to nominate. For the sake of benchmarking they don't need to be // actual validators let validators: Vec<_> = (0..n) .map(|i| account("stash", USER_SEED, i)) .collect(); whitelist_account!(depositor); }:_(RuntimeOrigin::Signed(depositor.clone()), 1, validators) verify { assert_eq!(RewardPools::::count(), 1); assert_eq!(BondedPools::::count(), 1); let (_, new_pool) = BondedPools::::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::Staking::active_stake(&Pools::::create_bonded_account(1)), Ok(min_create_bond) ); } set_state { // Create a pool let min_create_bond = Pools::::depositor_min_bond(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); BondedPools::::mutate(&1, |maybe_pool| { // Force the pool into an invalid state maybe_pool.as_mut().map(|pool| pool.points = min_create_bond * 10u32.into()); }); let caller = account("caller", 0, USER_SEED); whitelist_account!(caller); }:_(RuntimeOrigin::Signed(caller), 1, PoolState::Destroying) verify { assert_eq!(BondedPools::::get(1).unwrap().state, PoolState::Destroying); } set_metadata { let n in 1 .. ::MaxMetadataLen::get(); // Create a pool let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); // Create metadata of the max possible size let metadata: Vec = (0..n).map(|_| 42).collect(); whitelist_account!(depositor); }:_(RuntimeOrigin::Signed(depositor), 1, metadata.clone()) verify { assert_eq!(Metadata::::get(&1), metadata); } set_configs { }:_( RuntimeOrigin::Root, ConfigOp::Set(BalanceOf::::max_value()), ConfigOp::Set(BalanceOf::::max_value()), ConfigOp::Set(u32::MAX), ConfigOp::Set(u32::MAX), ConfigOp::Set(u32::MAX), ConfigOp::Set(Perbill::max_value()) ) verify { assert_eq!(MinJoinBond::::get(), BalanceOf::::max_value()); assert_eq!(MinCreateBond::::get(), BalanceOf::::max_value()); assert_eq!(MaxPools::::get(), Some(u32::MAX)); assert_eq!(MaxPoolMembers::::get(), Some(u32::MAX)); assert_eq!(MaxPoolMembersPerPool::::get(), Some(u32::MAX)); assert_eq!(GlobalMaxCommission::::get(), Some(Perbill::max_value())); } update_roles { let first_id = pallet_nomination_pools::LastPoolId::::get() + 1; let (root, _) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED); }:_( RuntimeOrigin::Signed(root.clone()), first_id, ConfigOp::Set(random.clone()), ConfigOp::Set(random.clone()), ConfigOp::Set(random.clone()) ) verify { assert_eq!( pallet_nomination_pools::BondedPools::::get(first_id).unwrap().roles, pallet_nomination_pools::PoolRoles { depositor: root, nominator: Some(random.clone()), bouncer: Some(random.clone()), root: Some(random), }, ) } chill { // Create a pool let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); // Nominate with the pool. let validators: Vec<_> = (0..MaxNominationsOf::::get()) .map(|i| account("stash", USER_SEED, i)) .collect(); assert_ok!(T::Staking::nominate(&pool_account, validators)); assert!(T::Staking::nominations(&Pools::::create_bonded_account(1)).is_some()); whitelist_account!(depositor); }:_(RuntimeOrigin::Signed(depositor.clone()), 1) verify { assert!(T::Staking::nominations(&Pools::::create_bonded_account(1)).is_none()); } set_commission { // Create a pool - do not set a commission yet. let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); // set a max commission Pools::::set_commission_max(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), Perbill::from_percent(50)).unwrap(); // set a change rate Pools::::set_commission_change_rate(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), CommissionChangeRate { max_increase: Perbill::from_percent(20), min_delay: 0u32.into(), }).unwrap(); // set a claim permission to an account. Pools::::set_commission_claim_permission( RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), Some(CommissionClaimPermission::Account(depositor.clone())) ).unwrap(); }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some((Perbill::from_percent(20), depositor.clone()))) verify { assert_eq!(BondedPools::::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)), }); } set_commission_max { // Create a pool, setting a commission that will update when max commission is set. let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), Some(Perbill::from_percent(50))); }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Perbill::from_percent(50)) verify { assert_eq!( BondedPools::::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, }); } set_commission_change_rate { // Create a pool let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), CommissionChangeRate { max_increase: Perbill::from_percent(50), min_delay: 1000u32.into(), }) verify { assert_eq!( BondedPools::::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, }); } set_commission_claim_permission { // Create a pool. let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some(CommissionClaimPermission::Account(depositor.clone()))) verify { assert_eq!( BondedPools::::get(1).unwrap().commission, Commission { current: None, max: None, change_rate: None, throttle_from: None, claim_permission: Some(CommissionClaimPermission::Account(depositor)), }); } set_claim_permission { // Create a pool let min_create_bond = Pools::::depositor_min_bond(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); // Join pool let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 4u32.into()); let joiner_lookup = T::Lookup::unlookup(joiner.clone()); Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1) .unwrap(); // Sanity check join worked assert_eq!( T::Staking::active_stake(&pool_account).unwrap(), min_create_bond + min_join_bond ); }:_(RuntimeOrigin::Signed(joiner.clone()), ClaimPermission::PermissionlessAll) verify { assert_eq!(ClaimPermissions::::get(joiner), ClaimPermission::PermissionlessAll); } claim_commission { let claimer: T::AccountId = account("claimer_member", USER_SEED + 4, 0); let commission = Perbill::from_percent(50); let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); let ed = CurrencyOf::::minimum_balance(); let (depositor, pool_account) = create_pool_account::(0, origin_weight, Some(commission)); let reward_account = Pools::::create_reward_account(1); CurrencyOf::::set_balance(&reward_account, ed + origin_weight); // member claims a payout to make some commission available. let _ = Pools::::claim_payout(RuntimeOrigin::Signed(claimer.clone()).into()); // set a claim permission to an account. let _ = Pools::::set_commission_claim_permission( RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), Some(CommissionClaimPermission::Account(claimer)) ); whitelist_account!(depositor); }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into()) verify { assert_eq!( CurrencyOf::::balance(&depositor), origin_weight + commission * origin_weight ); assert_eq!( CurrencyOf::::balance(&reward_account), ed + commission * origin_weight ); } adjust_pool_deposit { // Create a pool let (depositor, _) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); // Remove ed freeze to create a scenario where the ed deposit needs to be adjusted. let _ = Pools::::unfreeze_pool_deposit(&Pools::::create_reward_account(1)); assert!(&Pools::::check_ed_imbalance().is_err()); whitelist_account!(depositor); }:_(RuntimeOrigin::Signed(depositor), 1) verify { assert!(&Pools::::check_ed_imbalance().is_ok()); } impl_benchmark_test_suite!( Pallet, crate::mock::new_test_ext(), crate::mock::Runtime ); }