referrerpolicy=no-referrer-when-downgrade

pallet_nomination_pools_benchmarking/
inner.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Benchmarks for the nomination pools coupled with the staking and bags list pallets.
19
20use alloc::{vec, vec::Vec};
21use frame_benchmarking::v2::*;
22use frame_election_provider_support::SortedListProvider;
23use frame_support::{
24	assert_ok, ensure,
25	traits::{
26		fungible::{Inspect, Mutate, Unbalanced},
27		tokens::Preservation,
28		Get, Imbalance,
29	},
30};
31use frame_system::RawOrigin as RuntimeOrigin;
32use pallet_nomination_pools::{
33	adapter::{Member, Pool, StakeStrategy, StakeStrategyType},
34	BalanceOf, BondExtra, BondedPoolInner, BondedPools, ClaimPermission, ClaimPermissions,
35	Commission, CommissionChangeRate, CommissionClaimPermission, ConfigOp, GlobalMaxCommission,
36	MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond,
37	Pallet as Pools, PoolId, PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage,
38};
39use pallet_staking::MaxNominationsOf;
40use sp_runtime::{
41	traits::{Bounded, StaticLookup, Zero},
42	Perbill,
43};
44use sp_staking::{EraIndex, StakingUnchecked};
45// `frame_benchmarking::benchmarks!` macro needs this
46use pallet_nomination_pools::Call;
47
48type CurrencyOf<T> = <T as pallet_nomination_pools::Config>::Currency;
49
50const USER_SEED: u32 = 0;
51const MAX_SPANS: u32 = 100;
52
53pub(crate) type VoterBagsListInstance = pallet_bags_list::Instance1;
54pub trait Config:
55	pallet_nomination_pools::Config
56	+ pallet_staking::Config
57	+ pallet_bags_list::Config<VoterBagsListInstance>
58{
59}
60
61pub struct Pallet<T: Config>(Pools<T>);
62
63fn create_funded_user_with_balance<T: pallet_nomination_pools::Config>(
64	string: &'static str,
65	n: u32,
66	balance: BalanceOf<T>,
67) -> T::AccountId {
68	let user = account(string, n, USER_SEED);
69	T::Currency::set_balance(&user, balance);
70	user
71}
72
73// Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free
74// balance.
75fn create_pool_account<T: pallet_nomination_pools::Config>(
76	n: u32,
77	balance: BalanceOf<T>,
78	commission: Option<Perbill>,
79) -> (T::AccountId, T::AccountId) {
80	let ed = CurrencyOf::<T>::minimum_balance();
81	let pool_creator: T::AccountId =
82		create_funded_user_with_balance::<T>("pool_creator", n, ed + balance * 2u32.into());
83	let pool_creator_lookup = T::Lookup::unlookup(pool_creator.clone());
84
85	Pools::<T>::create(
86		RuntimeOrigin::Signed(pool_creator.clone()).into(),
87		balance,
88		pool_creator_lookup.clone(),
89		pool_creator_lookup.clone(),
90		pool_creator_lookup,
91	)
92	.unwrap();
93
94	if let Some(c) = commission {
95		let pool_id = pallet_nomination_pools::LastPoolId::<T>::get();
96		Pools::<T>::set_commission(
97			RuntimeOrigin::Signed(pool_creator.clone()).into(),
98			pool_id,
99			Some((c, pool_creator.clone())),
100		)
101		.expect("pool just created, commission can be set by root; qed");
102	}
103
104	let pool_account = pallet_nomination_pools::BondedPools::<T>::iter()
105		.find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator)
106		.map(|(pool_id, _)| Pools::<T>::generate_bonded_account(pool_id))
107		.expect("pool_creator created a pool above");
108
109	(pool_creator, pool_account)
110}
111
112fn migrate_to_transfer_stake<T: Config>(pool_id: PoolId) {
113	if T::StakeAdapter::strategy_type() == StakeStrategyType::Transfer {
114		// should already be in the correct strategy
115		return;
116	}
117	let pool_acc = Pools::<T>::generate_bonded_account(pool_id);
118	// drop the agent and its associated delegators .
119	T::StakeAdapter::remove_as_agent(Pool::from(pool_acc.clone()));
120
121	// tranfer funds from all members to the pool account.
122	PoolMembers::<T>::iter()
123		.filter(|(_, member)| member.pool_id == pool_id)
124		.for_each(|(member_acc, member)| {
125			let member_balance = member.total_balance();
126			<T as pallet_nomination_pools::Config>::Currency::transfer(
127				&member_acc,
128				&pool_acc,
129				member_balance,
130				Preservation::Preserve,
131			)
132			.expect("member should have enough balance to transfer");
133		});
134
135	// Pool needs to have ED balance free to stake so give it some.
136	// Note: we didn't require ED until pallet-staking migrated from locks to holds.
137	let _ = CurrencyOf::<T>::mint_into(&pool_acc, CurrencyOf::<T>::minimum_balance());
138
139	pallet_staking::Pallet::<T>::migrate_to_direct_staker(&pool_acc);
140}
141
142fn vote_to_balance<T: pallet_nomination_pools::Config>(
143	vote: u64,
144) -> Result<BalanceOf<T>, &'static str> {
145	vote.try_into().map_err(|_| "could not convert u64 to Balance")
146}
147
148#[allow(unused)]
149struct ListScenario<T: pallet_nomination_pools::Config> {
150	/// Stash/Controller that is expected to be moved.
151	origin1: T::AccountId,
152	creator1: T::AccountId,
153	dest_weight: BalanceOf<T>,
154	origin1_member: Option<T::AccountId>,
155}
156
157impl<T: Config> ListScenario<T> {
158	/// An expensive scenario for bags-list implementation:
159	///
160	/// - the node to be updated (r) is the head of a bag that has at least one other node. The bag
161	///   itself will need to be read and written to update its head. The node pointed to by r.next
162	///   will need to be read and written as it will need to have its prev pointer updated. Note
163	///   that there are two other worst case scenarios for bag removal: 1) the node is a tail and
164	///   2) the node is a middle node with prev and next; all scenarios end up with the same number
165	///   of storage reads and writes.
166	///
167	/// - the destination bag has at least one node, which will need its next pointer updated.
168	pub(crate) fn new(
169		origin_weight: BalanceOf<T>,
170		is_increase: bool,
171	) -> Result<Self, &'static str> {
172		ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0");
173
174		ensure!(
175			pallet_nomination_pools::MaxPools::<T>::get().unwrap_or(0) >= 3,
176			"must allow at least three pools for benchmarks"
177		);
178
179		// Burn the entire issuance.
180		CurrencyOf::<T>::set_total_issuance(Zero::zero());
181
182		// Create accounts with the origin weight
183		let (pool_creator1, pool_origin1) =
184			create_pool_account::<T>(USER_SEED + 1, origin_weight, Some(Perbill::from_percent(50)));
185
186		T::StakeAdapter::nominate(
187			Pool::from(pool_origin1.clone()),
188			// NOTE: these don't really need to be validators.
189			vec![account("random_validator", 0, USER_SEED)],
190		)?;
191
192		let (_, pool_origin2) =
193			create_pool_account::<T>(USER_SEED + 2, origin_weight, Some(Perbill::from_percent(50)));
194
195		T::StakeAdapter::nominate(
196			Pool::from(pool_origin2.clone()),
197			vec![account("random_validator", 0, USER_SEED)].clone(),
198		)?;
199
200		// Find a destination weight that will trigger the worst case scenario
201		let dest_weight_as_vote = <T as pallet_staking::Config>::VoterList::score_update_worst_case(
202			&pool_origin1,
203			is_increase,
204		);
205
206		let dest_weight: BalanceOf<T> =
207			dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?;
208
209		// Create an account with the worst case destination weight
210		let (_, pool_dest1) =
211			create_pool_account::<T>(USER_SEED + 3, dest_weight, Some(Perbill::from_percent(50)));
212
213		T::StakeAdapter::nominate(
214			Pool::from(pool_dest1.clone()),
215			vec![account("random_validator", 0, USER_SEED)],
216		)?;
217
218		let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
219		assert_eq!(vote_to_balance::<T>(weight_of(&pool_origin1)).unwrap(), origin_weight);
220		assert_eq!(vote_to_balance::<T>(weight_of(&pool_origin2)).unwrap(), origin_weight);
221		assert_eq!(vote_to_balance::<T>(weight_of(&pool_dest1)).unwrap(), dest_weight);
222
223		Ok(ListScenario {
224			origin1: pool_origin1,
225			creator1: pool_creator1,
226			dest_weight,
227			origin1_member: None,
228		})
229	}
230
231	fn add_joiner(mut self, amount: BalanceOf<T>) -> Self {
232		let amount = MinJoinBond::<T>::get()
233			.max(CurrencyOf::<T>::minimum_balance())
234			// Max `amount` with minimum thresholds for account balance and joining a pool
235			// to ensure 1) the user can be created and 2) can join the pool
236			.max(amount);
237
238		let joiner: T::AccountId = account("joiner", USER_SEED, 0);
239		self.origin1_member = Some(joiner.clone());
240		CurrencyOf::<T>::set_balance(&joiner, amount * 2u32.into());
241
242		let original_bonded = T::StakeAdapter::active_stake(Pool::from(self.origin1.clone()));
243
244		// Unbond `amount` from the underlying pool account so when the member joins
245		// we will maintain `current_bonded`.
246		T::StakeAdapter::unbond(Pool::from(self.origin1.clone()), amount)
247			.expect("the pool was created in `Self::new`.");
248
249		// Account pool points for the unbonded balance.
250		BondedPools::<T>::mutate(&1, |maybe_pool| {
251			maybe_pool.as_mut().map(|pool| pool.points -= amount)
252		});
253
254		Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), amount, 1).unwrap();
255
256		// check that the vote weight is still the same as the original bonded
257		let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
258		assert_eq!(vote_to_balance::<T>(weight_of(&self.origin1)).unwrap(), original_bonded);
259
260		// check the member was added correctly
261		let member = PoolMembers::<T>::get(&joiner).unwrap();
262		assert_eq!(member.points, amount);
263		assert_eq!(member.pool_id, 1);
264
265		self
266	}
267}
268
269#[benchmarks(
270	where
271		T: pallet_staking::Config,
272		pallet_staking::BalanceOf<T>: From<u128>,
273		BalanceOf<T>: Into<u128>,
274)]
275mod benchmarks {
276	use super::*;
277
278	#[benchmark]
279	fn join() {
280		let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
281
282		// setup the worst case list scenario.
283		let scenario = ListScenario::<T>::new(origin_weight, true).unwrap();
284		assert_eq!(
285			T::StakeAdapter::active_stake(Pool::from(scenario.origin1.clone())),
286			origin_weight
287		);
288
289		let max_additional = scenario.dest_weight - origin_weight;
290		let joiner_free = CurrencyOf::<T>::minimum_balance() + max_additional;
291
292		let joiner: T::AccountId = create_funded_user_with_balance::<T>("joiner", 0, joiner_free);
293
294		whitelist_account!(joiner);
295
296		#[extrinsic_call]
297		_(RuntimeOrigin::Signed(joiner.clone()), max_additional, 1);
298
299		assert_eq!(CurrencyOf::<T>::balance(&joiner), joiner_free - max_additional);
300		assert_eq!(
301			T::StakeAdapter::active_stake(Pool::from(scenario.origin1)),
302			scenario.dest_weight
303		);
304	}
305
306	#[benchmark]
307	fn bond_extra_transfer() {
308		let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
309		let scenario = ListScenario::<T>::new(origin_weight, true).unwrap();
310		let extra = scenario.dest_weight - origin_weight;
311
312		// creator of the src pool will bond-extra, bumping itself to dest bag.
313
314		#[extrinsic_call]
315		bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra));
316
317		assert!(
318			T::StakeAdapter::active_stake(Pool::from(scenario.origin1)) >= scenario.dest_weight
319		);
320	}
321
322	#[benchmark]
323	fn bond_extra_other() {
324		let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0);
325
326		let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
327		let scenario = ListScenario::<T>::new(origin_weight, true).unwrap();
328		let extra = (scenario.dest_weight - origin_weight).max(CurrencyOf::<T>::minimum_balance());
329
330		// set claim preferences to `PermissionlessAll` to any account to bond extra on member's
331		// behalf.
332		let _ = Pools::<T>::set_claim_permission(
333			RuntimeOrigin::Signed(scenario.creator1.clone()).into(),
334			ClaimPermission::PermissionlessAll,
335		);
336
337		// transfer exactly `extra` to the depositor of the src pool (1),
338		let reward_account1 = Pools::<T>::generate_reward_account(1);
339		assert!(extra >= CurrencyOf::<T>::minimum_balance());
340		let _ = CurrencyOf::<T>::mint_into(&reward_account1, extra);
341
342		#[extrinsic_call]
343		_(
344			RuntimeOrigin::Signed(claimer),
345			T::Lookup::unlookup(scenario.creator1.clone()),
346			BondExtra::Rewards,
347		);
348
349		// commission of 50% deducted here.
350		assert!(
351			T::StakeAdapter::active_stake(Pool::from(scenario.origin1)) >=
352				scenario.dest_weight / 2u32.into()
353		);
354	}
355
356	#[benchmark]
357	fn claim_payout() {
358		let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0);
359		let commission = Perbill::from_percent(50);
360		let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
361		let ed = CurrencyOf::<T>::minimum_balance();
362		let (depositor, _pool_account) =
363			create_pool_account::<T>(0, origin_weight, Some(commission));
364		let reward_account = Pools::<T>::generate_reward_account(1);
365
366		// Send funds to the reward account of the pool
367		CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
368
369		// set claim preferences to `PermissionlessAll` so any account can claim rewards on member's
370		// behalf.
371		let _ = Pools::<T>::set_claim_permission(
372			RuntimeOrigin::Signed(depositor.clone()).into(),
373			ClaimPermission::PermissionlessAll,
374		);
375
376		// Sanity check
377		assert_eq!(CurrencyOf::<T>::balance(&depositor), origin_weight);
378
379		whitelist_account!(depositor);
380
381		#[extrinsic_call]
382		claim_payout_other(RuntimeOrigin::Signed(claimer), depositor.clone());
383
384		assert_eq!(
385			CurrencyOf::<T>::balance(&depositor),
386			origin_weight + commission * origin_weight
387		);
388		assert_eq!(CurrencyOf::<T>::balance(&reward_account), ed + commission * origin_weight);
389	}
390
391	#[benchmark]
392	fn unbond() {
393		// The weight the nominator will start at. The value used here is expected to be
394		// significantly higher than the first position in a list (e.g. the first bag threshold).
395		let origin_weight = Pools::<T>::depositor_min_bond() * 200u32.into();
396		let scenario = ListScenario::<T>::new(origin_weight, false).unwrap();
397		let amount = origin_weight - scenario.dest_weight;
398
399		let scenario = scenario.add_joiner(amount);
400		let member_id = scenario.origin1_member.unwrap().clone();
401		let member_id_lookup = T::Lookup::unlookup(member_id.clone());
402		let all_points = PoolMembers::<T>::get(&member_id).unwrap().points;
403		whitelist_account!(member_id);
404
405		#[extrinsic_call]
406		_(RuntimeOrigin::Signed(member_id.clone()), member_id_lookup, all_points);
407
408		let bonded_after = T::StakeAdapter::active_stake(Pool::from(scenario.origin1));
409		// We at least went down to the destination bag
410		assert!(bonded_after <= scenario.dest_weight);
411		let member = PoolMembers::<T>::get(&member_id).unwrap();
412		assert_eq!(
413			member.unbonding_eras.keys().cloned().collect::<Vec<_>>(),
414			vec![0 + T::StakeAdapter::bonding_duration()]
415		);
416		assert_eq!(member.unbonding_eras.values().cloned().collect::<Vec<_>>(), vec![all_points]);
417	}
418
419	#[benchmark]
420	fn pool_withdraw_unbonded(s: Linear<0, MAX_SPANS>) {
421		let min_create_bond = Pools::<T>::depositor_min_bond();
422		let (_depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
423
424		// Add a new member
425		let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
426		let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 2u32.into());
427		Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1).unwrap();
428
429		// Sanity check join worked
430		assert_eq!(
431			T::StakeAdapter::active_stake(Pool::from(pool_account.clone())),
432			min_create_bond + min_join_bond
433		);
434		assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
435
436		// Unbond the new member
437		Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone())
438			.unwrap();
439
440		// Sanity check that unbond worked
441		assert_eq!(
442			T::StakeAdapter::active_stake(Pool::from(pool_account.clone())),
443			min_create_bond
444		);
445		assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
446		// Set the current era
447		pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
448
449		// Add `s` count of slashing spans to storage.
450		pallet_staking::benchmarking::add_slashing_spans::<T>(&pool_account, s);
451		whitelist_account!(pool_account);
452
453		#[extrinsic_call]
454		_(RuntimeOrigin::Signed(pool_account.clone()), 1, s);
455
456		// The joiners funds didn't change
457		assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
458		// The unlocking chunk was removed
459		assert_eq!(pallet_staking::Ledger::<T>::get(pool_account).unwrap().unlocking.len(), 0);
460	}
461
462	#[benchmark]
463	fn withdraw_unbonded_update(s: Linear<0, MAX_SPANS>) {
464		let min_create_bond = Pools::<T>::depositor_min_bond();
465		let (_depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
466
467		// Add a new member
468		let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
469		let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 2u32.into());
470		let joiner_lookup = T::Lookup::unlookup(joiner.clone());
471		Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1).unwrap();
472
473		// Sanity check join worked
474		assert_eq!(
475			T::StakeAdapter::active_stake(Pool::from(pool_account.clone())),
476			min_create_bond + min_join_bond
477		);
478		assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
479
480		// Unbond the new member
481		pallet_staking::CurrentEra::<T>::put(0);
482		Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone())
483			.unwrap();
484
485		// Sanity check that unbond worked
486		assert_eq!(
487			T::StakeAdapter::active_stake(Pool::from(pool_account.clone())),
488			min_create_bond
489		);
490		assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
491
492		// Set the current era to ensure we can withdraw unbonded funds
493		pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
494
495		pallet_staking::benchmarking::add_slashing_spans::<T>(&pool_account, s);
496		whitelist_account!(joiner);
497
498		#[extrinsic_call]
499		withdraw_unbonded(RuntimeOrigin::Signed(joiner.clone()), joiner_lookup, s);
500
501		assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond * 2u32.into());
502		// The unlocking chunk was removed
503		assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 0);
504	}
505
506	#[benchmark]
507	fn withdraw_unbonded_kill(s: Linear<0, MAX_SPANS>) {
508		let min_create_bond = Pools::<T>::depositor_min_bond();
509		let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
510		let depositor_lookup = T::Lookup::unlookup(depositor.clone());
511
512		// We set the pool to the destroying state so the depositor can leave
513		BondedPools::<T>::try_mutate(&1, |maybe_bonded_pool| {
514			maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| {
515				bonded_pool.state = PoolState::Destroying;
516			})
517		})
518		.unwrap();
519
520		// Unbond the creator
521		pallet_staking::CurrentEra::<T>::put(0);
522		// Simulate some rewards so we can check if the rewards storage is cleaned up. We check this
523		// here to ensure the complete flow for destroying a pool works - the reward pool account
524		// should never exist by time the depositor withdraws so we test that it gets cleaned
525		// up when unbonding.
526		let reward_account = Pools::<T>::generate_reward_account(1);
527		assert!(frame_system::Account::<T>::contains_key(&reward_account));
528		Pools::<T>::fully_unbond(
529			RuntimeOrigin::Signed(depositor.clone()).into(),
530			depositor.clone(),
531		)
532		.unwrap();
533
534		// Sanity check that unbond worked
535		assert_eq!(T::StakeAdapter::active_stake(Pool::from(pool_account.clone())), Zero::zero());
536		assert_eq!(
537			T::StakeAdapter::total_balance(Pool::from(pool_account.clone())),
538			Some(min_create_bond)
539		);
540		assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
541
542		// Set the current era to ensure we can withdraw unbonded funds
543		pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
544
545		// Some last checks that storage items we expect to get cleaned up are present
546		assert!(pallet_staking::Ledger::<T>::contains_key(&pool_account));
547		assert!(BondedPools::<T>::contains_key(&1));
548		assert!(SubPoolsStorage::<T>::contains_key(&1));
549		assert!(RewardPools::<T>::contains_key(&1));
550		assert!(PoolMembers::<T>::contains_key(&depositor));
551		assert!(frame_system::Account::<T>::contains_key(&reward_account));
552
553		whitelist_account!(depositor);
554
555		#[extrinsic_call]
556		withdraw_unbonded(RuntimeOrigin::Signed(depositor.clone()), depositor_lookup, s);
557
558		// Pool removal worked
559		assert!(!pallet_staking::Ledger::<T>::contains_key(&pool_account));
560		assert!(!BondedPools::<T>::contains_key(&1));
561		assert!(!SubPoolsStorage::<T>::contains_key(&1));
562		assert!(!RewardPools::<T>::contains_key(&1));
563		assert!(!PoolMembers::<T>::contains_key(&depositor));
564		assert!(!frame_system::Account::<T>::contains_key(&pool_account));
565		assert!(!frame_system::Account::<T>::contains_key(&reward_account));
566
567		// Funds where transferred back correctly
568		assert_eq!(
569			CurrencyOf::<T>::balance(&depositor),
570			// gets bond back + rewards collecting when unbonding
571			min_create_bond * 2u32.into() + CurrencyOf::<T>::minimum_balance()
572		);
573	}
574
575	#[benchmark]
576	fn create() {
577		let min_create_bond = Pools::<T>::depositor_min_bond();
578		let depositor: T::AccountId = account("depositor", USER_SEED, 0);
579		let depositor_lookup = T::Lookup::unlookup(depositor.clone());
580
581		// Give the depositor some balance to bond
582		// it needs to transfer min balance to reward account as well so give additional min
583		// balance.
584		CurrencyOf::<T>::set_balance(
585			&depositor,
586			min_create_bond + CurrencyOf::<T>::minimum_balance() * 2u32.into(),
587		);
588		// Make sure no Pools exist at a pre-condition for our verify checks
589		assert_eq!(RewardPools::<T>::count(), 0);
590		assert_eq!(BondedPools::<T>::count(), 0);
591
592		whitelist_account!(depositor);
593
594		#[extrinsic_call]
595		_(
596			RuntimeOrigin::Signed(depositor.clone()),
597			min_create_bond,
598			depositor_lookup.clone(),
599			depositor_lookup.clone(),
600			depositor_lookup,
601		);
602
603		assert_eq!(RewardPools::<T>::count(), 1);
604		assert_eq!(BondedPools::<T>::count(), 1);
605		let (_, new_pool) = BondedPools::<T>::iter().next().unwrap();
606		assert_eq!(
607			new_pool,
608			BondedPoolInner {
609				commission: Commission::default(),
610				member_counter: 1,
611				points: min_create_bond,
612				roles: PoolRoles {
613					depositor: depositor.clone(),
614					root: Some(depositor.clone()),
615					nominator: Some(depositor.clone()),
616					bouncer: Some(depositor.clone()),
617				},
618				state: PoolState::Open,
619			}
620		);
621		assert_eq!(
622			T::StakeAdapter::active_stake(Pool::from(Pools::<T>::generate_bonded_account(1))),
623			min_create_bond
624		);
625	}
626
627	#[benchmark]
628	fn nominate(n: Linear<1, { MaxNominationsOf::<T>::get() }>) {
629		// Create a pool
630		let min_create_bond = Pools::<T>::depositor_min_bond() * 2u32.into();
631		let (depositor, _pool_account) = create_pool_account::<T>(0, min_create_bond, None);
632
633		// Create some accounts to nominate. For the sake of benchmarking they don't need to be
634		// actual validators
635		let validators: Vec<_> = (0..n).map(|i| account("stash", USER_SEED, i)).collect();
636
637		whitelist_account!(depositor);
638
639		#[extrinsic_call]
640		_(RuntimeOrigin::Signed(depositor.clone()), 1, validators);
641
642		assert_eq!(RewardPools::<T>::count(), 1);
643		assert_eq!(BondedPools::<T>::count(), 1);
644		let (_, new_pool) = BondedPools::<T>::iter().next().unwrap();
645		assert_eq!(
646			new_pool,
647			BondedPoolInner {
648				commission: Commission::default(),
649				member_counter: 1,
650				points: min_create_bond,
651				roles: PoolRoles {
652					depositor: depositor.clone(),
653					root: Some(depositor.clone()),
654					nominator: Some(depositor.clone()),
655					bouncer: Some(depositor.clone()),
656				},
657				state: PoolState::Open,
658			}
659		);
660		assert_eq!(
661			T::StakeAdapter::active_stake(Pool::from(Pools::<T>::generate_bonded_account(1))),
662			min_create_bond
663		);
664	}
665
666	#[benchmark]
667	fn set_state() {
668		// Create a pool
669		let min_create_bond = Pools::<T>::depositor_min_bond();
670		// Don't need the accounts, but the pool.
671		let _ = create_pool_account::<T>(0, min_create_bond, None);
672		BondedPools::<T>::mutate(&1, |maybe_pool| {
673			// Force the pool into an invalid state
674			maybe_pool.as_mut().map(|pool| pool.points = min_create_bond * 10u32.into());
675		});
676
677		let caller = account("caller", 0, USER_SEED);
678		whitelist_account!(caller);
679
680		#[extrinsic_call]
681		_(RuntimeOrigin::Signed(caller), 1, PoolState::Destroying);
682
683		assert_eq!(BondedPools::<T>::get(1).unwrap().state, PoolState::Destroying);
684	}
685
686	#[benchmark]
687	fn set_metadata(
688		n: Linear<1, { <T as pallet_nomination_pools::Config>::MaxMetadataLen::get() }>,
689	) {
690		// Create a pool
691		let (depositor, _pool_account) =
692			create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
693
694		// Create metadata of the max possible size
695		let metadata: Vec<u8> = (0..n).map(|_| 42).collect();
696
697		whitelist_account!(depositor);
698
699		#[extrinsic_call]
700		_(RuntimeOrigin::Signed(depositor), 1, metadata.clone());
701		assert_eq!(Metadata::<T>::get(&1), metadata);
702	}
703
704	#[benchmark]
705	fn set_configs() {
706		#[extrinsic_call]
707		_(
708			RuntimeOrigin::Root,
709			ConfigOp::Set(BalanceOf::<T>::max_value()),
710			ConfigOp::Set(BalanceOf::<T>::max_value()),
711			ConfigOp::Set(u32::MAX),
712			ConfigOp::Set(u32::MAX),
713			ConfigOp::Set(u32::MAX),
714			ConfigOp::Set(Perbill::max_value()),
715		);
716
717		assert_eq!(MinJoinBond::<T>::get(), BalanceOf::<T>::max_value());
718		assert_eq!(MinCreateBond::<T>::get(), BalanceOf::<T>::max_value());
719		assert_eq!(MaxPools::<T>::get(), Some(u32::MAX));
720		assert_eq!(MaxPoolMembers::<T>::get(), Some(u32::MAX));
721		assert_eq!(MaxPoolMembersPerPool::<T>::get(), Some(u32::MAX));
722		assert_eq!(GlobalMaxCommission::<T>::get(), Some(Perbill::max_value()));
723	}
724
725	#[benchmark]
726	fn update_roles() {
727		let first_id = pallet_nomination_pools::LastPoolId::<T>::get() + 1;
728		let (root, _) =
729			create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
730		let random: T::AccountId =
731			account("but is anything really random in computers..?", 0, USER_SEED);
732
733		#[extrinsic_call]
734		_(
735			RuntimeOrigin::Signed(root.clone()),
736			first_id,
737			ConfigOp::Set(random.clone()),
738			ConfigOp::Set(random.clone()),
739			ConfigOp::Set(random.clone()),
740		);
741		assert_eq!(
742			pallet_nomination_pools::BondedPools::<T>::get(first_id).unwrap().roles,
743			pallet_nomination_pools::PoolRoles {
744				depositor: root,
745				nominator: Some(random.clone()),
746				bouncer: Some(random.clone()),
747				root: Some(random),
748			},
749		)
750	}
751
752	#[benchmark]
753	fn chill() {
754		// Create a pool
755		let (depositor, pool_account) =
756			create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
757
758		// Nominate with the pool.
759		let validators: Vec<_> = (0..MaxNominationsOf::<T>::get())
760			.map(|i| account("stash", USER_SEED, i))
761			.collect();
762
763		assert_ok!(T::StakeAdapter::nominate(Pool::from(pool_account.clone()), validators));
764		assert!(T::StakeAdapter::nominations(Pool::from(pool_account.clone())).is_some());
765
766		whitelist_account!(depositor);
767
768		#[extrinsic_call]
769		_(RuntimeOrigin::Signed(depositor.clone()), 1);
770
771		assert!(T::StakeAdapter::nominations(Pool::from(pool_account.clone())).is_none());
772	}
773
774	#[benchmark]
775	fn set_commission() {
776		// Create a pool - do not set a commission yet.
777		let (depositor, _pool_account) =
778			create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
779		// set a max commission
780		Pools::<T>::set_commission_max(
781			RuntimeOrigin::Signed(depositor.clone()).into(),
782			1u32.into(),
783			Perbill::from_percent(50),
784		)
785		.unwrap();
786		// set a change rate
787		Pools::<T>::set_commission_change_rate(
788			RuntimeOrigin::Signed(depositor.clone()).into(),
789			1u32.into(),
790			CommissionChangeRate {
791				max_increase: Perbill::from_percent(20),
792				min_delay: 0u32.into(),
793			},
794		)
795		.unwrap();
796		// set a claim permission to an account.
797		Pools::<T>::set_commission_claim_permission(
798			RuntimeOrigin::Signed(depositor.clone()).into(),
799			1u32.into(),
800			Some(CommissionClaimPermission::Account(depositor.clone())),
801		)
802		.unwrap();
803
804		#[extrinsic_call]
805		_(
806			RuntimeOrigin::Signed(depositor.clone()),
807			1u32.into(),
808			Some((Perbill::from_percent(20), depositor.clone())),
809		);
810
811		assert_eq!(
812			BondedPools::<T>::get(1).unwrap().commission,
813			Commission {
814				current: Some((Perbill::from_percent(20), depositor.clone())),
815				max: Some(Perbill::from_percent(50)),
816				change_rate: Some(CommissionChangeRate {
817					max_increase: Perbill::from_percent(20),
818					min_delay: 0u32.into()
819				}),
820				throttle_from: Some(1u32.into()),
821				claim_permission: Some(CommissionClaimPermission::Account(depositor)),
822			}
823		);
824	}
825
826	#[benchmark]
827	fn set_commission_max() {
828		// Create a pool, setting a commission that will update when max commission is set.
829		let (depositor, _pool_account) = create_pool_account::<T>(
830			0,
831			Pools::<T>::depositor_min_bond() * 2u32.into(),
832			Some(Perbill::from_percent(50)),
833		);
834
835		#[extrinsic_call]
836		_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Perbill::from_percent(50));
837
838		assert_eq!(
839			BondedPools::<T>::get(1).unwrap().commission,
840			Commission {
841				current: Some((Perbill::from_percent(50), depositor)),
842				max: Some(Perbill::from_percent(50)),
843				change_rate: None,
844				throttle_from: Some(0u32.into()),
845				claim_permission: None,
846			}
847		);
848	}
849
850	#[benchmark]
851	fn set_commission_change_rate() {
852		// Create a pool
853		let (depositor, _pool_account) =
854			create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
855
856		#[extrinsic_call]
857		_(
858			RuntimeOrigin::Signed(depositor.clone()),
859			1u32.into(),
860			CommissionChangeRate {
861				max_increase: Perbill::from_percent(50),
862				min_delay: 1000u32.into(),
863			},
864		);
865
866		assert_eq!(
867			BondedPools::<T>::get(1).unwrap().commission,
868			Commission {
869				current: None,
870				max: None,
871				change_rate: Some(CommissionChangeRate {
872					max_increase: Perbill::from_percent(50),
873					min_delay: 1000u32.into(),
874				}),
875				throttle_from: Some(1_u32.into()),
876				claim_permission: None,
877			}
878		);
879	}
880
881	#[benchmark]
882	fn set_commission_claim_permission() {
883		// Create a pool.
884		let (depositor, _pool_account) =
885			create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
886
887		#[extrinsic_call]
888		_(
889			RuntimeOrigin::Signed(depositor.clone()),
890			1u32.into(),
891			Some(CommissionClaimPermission::Account(depositor.clone())),
892		);
893
894		assert_eq!(
895			BondedPools::<T>::get(1).unwrap().commission,
896			Commission {
897				current: None,
898				max: None,
899				change_rate: None,
900				throttle_from: None,
901				claim_permission: Some(CommissionClaimPermission::Account(depositor)),
902			}
903		);
904	}
905
906	#[benchmark]
907	fn set_claim_permission() {
908		// Create a pool
909		let min_create_bond = Pools::<T>::depositor_min_bond();
910		let (_depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
911
912		// Join pool
913		let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
914		let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 4u32.into());
915		Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1).unwrap();
916
917		// Sanity check join worked
918		assert_eq!(
919			T::StakeAdapter::active_stake(Pool::from(pool_account.clone())),
920			min_create_bond + min_join_bond
921		);
922
923		#[extrinsic_call]
924		_(RuntimeOrigin::Signed(joiner.clone()), ClaimPermission::Permissioned);
925
926		assert_eq!(ClaimPermissions::<T>::get(joiner), ClaimPermission::Permissioned);
927	}
928
929	#[benchmark]
930	fn claim_commission() {
931		let claimer: T::AccountId = account("claimer_member", USER_SEED + 4, 0);
932		let commission = Perbill::from_percent(50);
933		let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
934		let ed = CurrencyOf::<T>::minimum_balance();
935		let (depositor, _pool_account) =
936			create_pool_account::<T>(0, origin_weight, Some(commission));
937		let reward_account = Pools::<T>::generate_reward_account(1);
938		CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
939
940		// member claims a payout to make some commission available.
941		let _ = Pools::<T>::claim_payout(RuntimeOrigin::Signed(claimer.clone()).into());
942		// set a claim permission to an account.
943		let _ = Pools::<T>::set_commission_claim_permission(
944			RuntimeOrigin::Signed(depositor.clone()).into(),
945			1u32.into(),
946			Some(CommissionClaimPermission::Account(claimer)),
947		);
948		whitelist_account!(depositor);
949
950		#[extrinsic_call]
951		_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into());
952
953		assert_eq!(
954			CurrencyOf::<T>::balance(&depositor),
955			origin_weight + commission * origin_weight
956		);
957		assert_eq!(CurrencyOf::<T>::balance(&reward_account), ed + commission * origin_weight);
958	}
959
960	#[benchmark]
961	fn adjust_pool_deposit() {
962		// Create a pool
963		let (depositor, _) =
964			create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
965
966		// Remove ed freeze to create a scenario where the ed deposit needs to be adjusted.
967		let _ = Pools::<T>::unfreeze_pool_deposit(&Pools::<T>::generate_reward_account(1));
968
969		assert_eq!(Pools::<T>::check_ed_imbalance().unwrap(), 1);
970
971		whitelist_account!(depositor);
972
973		#[extrinsic_call]
974		_(RuntimeOrigin::Signed(depositor), 1);
975
976		assert_eq!(Pools::<T>::check_ed_imbalance().unwrap(), 0);
977	}
978
979	#[benchmark]
980	fn apply_slash() {
981		// We want to fill member's unbonding pools. So let's bond with big enough amount.
982		let deposit_amount =
983			Pools::<T>::depositor_min_bond() * T::MaxUnbonding::get().into() * 4u32.into();
984		let (depositor, pool_account) = create_pool_account::<T>(0, deposit_amount, None);
985		let depositor_lookup = T::Lookup::unlookup(depositor.clone());
986
987		// verify user balance in the pool.
988		assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount);
989		// verify delegated balance.
990		assert!(
991			T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
992				Some(deposit_amount),
993		);
994
995		// ugly type conversion between balances of pallet staking and pools (which really are same
996		// type). Maybe there is a better way?
997		let slash_amount: u128 = deposit_amount.into() / 2;
998
999		// slash pool by half
1000		pallet_staking::slashing::do_slash::<T>(
1001			&pool_account,
1002			slash_amount.into(),
1003			&mut pallet_staking::BalanceOf::<T>::zero(),
1004			&mut pallet_staking::NegativeImbalanceOf::<T>::zero(),
1005			EraIndex::zero(),
1006		);
1007
1008		// verify user balance is slashed in the pool.
1009		assert_eq!(
1010			PoolMembers::<T>::get(&depositor).unwrap().total_balance(),
1011			deposit_amount / 2u32.into()
1012		);
1013		// verify delegated balance are not yet slashed.
1014		assert!(
1015			T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
1016				Some(deposit_amount),
1017		);
1018
1019		// Fill member's sub pools for the worst case.
1020		for i in 1..(T::MaxUnbonding::get() + 1) {
1021			pallet_staking::CurrentEra::<T>::put(i);
1022			assert!(Pools::<T>::unbond(
1023				RuntimeOrigin::Signed(depositor.clone()).into(),
1024				depositor_lookup.clone(),
1025				Pools::<T>::depositor_min_bond()
1026			)
1027			.is_ok());
1028		}
1029
1030		pallet_staking::CurrentEra::<T>::put(T::MaxUnbonding::get() + 2);
1031
1032		let slash_reporter =
1033			create_funded_user_with_balance::<T>("slasher", 0, CurrencyOf::<T>::minimum_balance());
1034		whitelist_account!(depositor);
1035
1036		#[block]
1037		{
1038			assert!(Pools::<T>::apply_slash(
1039				RuntimeOrigin::Signed(slash_reporter.clone()).into(),
1040				depositor_lookup.clone(),
1041			)
1042			.is_ok(),);
1043		}
1044
1045		// verify balances are correct and slash applied.
1046		assert_eq!(
1047			PoolMembers::<T>::get(&depositor).unwrap().total_balance(),
1048			deposit_amount / 2u32.into()
1049		);
1050		assert!(
1051			T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
1052				Some(deposit_amount / 2u32.into()),
1053		);
1054	}
1055
1056	#[benchmark]
1057	fn apply_slash_fail() {
1058		// Bench the scenario where pool has some unapplied slash but the member does not have any
1059		// slash to be applied.
1060		let deposit_amount = Pools::<T>::depositor_min_bond() * 10u32.into();
1061		// Create pool.
1062		let (_depositor, pool_account) = create_pool_account::<T>(0, deposit_amount, None);
1063
1064		// slash pool by half
1065		let slash_amount: u128 = deposit_amount.into() / 2;
1066		pallet_staking::slashing::do_slash::<T>(
1067			&pool_account,
1068			slash_amount.into(),
1069			&mut pallet_staking::BalanceOf::<T>::zero(),
1070			&mut pallet_staking::NegativeImbalanceOf::<T>::zero(),
1071			EraIndex::zero(),
1072		);
1073
1074		pallet_staking::CurrentEra::<T>::put(1);
1075
1076		// new member joins the pool who should not be affected by slash.
1077		let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
1078		let join_amount = min_join_bond * T::MaxUnbonding::get().into() * 2u32.into();
1079		let joiner = create_funded_user_with_balance::<T>("joiner", 0, join_amount * 2u32.into());
1080		let joiner_lookup = T::Lookup::unlookup(joiner.clone());
1081		assert!(
1082			Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), join_amount, 1).is_ok()
1083		);
1084
1085		// Fill member's sub pools for the worst case.
1086		for i in 0..T::MaxUnbonding::get() {
1087			pallet_staking::CurrentEra::<T>::put(i + 2); // +2 because we already set the current era to 1.
1088			assert!(Pools::<T>::unbond(
1089				RuntimeOrigin::Signed(joiner.clone()).into(),
1090				joiner_lookup.clone(),
1091				min_join_bond
1092			)
1093			.is_ok());
1094		}
1095
1096		pallet_staking::CurrentEra::<T>::put(T::MaxUnbonding::get() + 3);
1097		whitelist_account!(joiner);
1098
1099		// Since the StakeAdapter can be different based on the runtime config, the errors could be
1100		// different as well.
1101		#[block]
1102		{
1103			assert!(Pools::<T>::apply_slash(
1104				RuntimeOrigin::Signed(joiner.clone()).into(),
1105				joiner_lookup.clone()
1106			)
1107			.is_err());
1108		}
1109	}
1110
1111	#[benchmark]
1112	fn pool_migrate() {
1113		// create a pool.
1114		let deposit_amount = Pools::<T>::depositor_min_bond() * 2u32.into();
1115		let (depositor, pool_account) = create_pool_account::<T>(0, deposit_amount, None);
1116
1117		// migrate pool to transfer stake.
1118		let _ = migrate_to_transfer_stake::<T>(1);
1119		#[block]
1120		{
1121			assert!(Pools::<T>::migrate_pool_to_delegate_stake(
1122				RuntimeOrigin::Signed(depositor.clone()).into(),
1123				1u32.into(),
1124			)
1125			.is_ok(),);
1126		}
1127		// this queries agent balance.
1128		assert_eq!(
1129			T::StakeAdapter::total_balance(Pool::from(pool_account.clone())),
1130			Some(deposit_amount + CurrencyOf::<T>::minimum_balance())
1131		);
1132	}
1133
1134	#[benchmark]
1135	fn migrate_delegation() {
1136		// create a pool.
1137		let deposit_amount = Pools::<T>::depositor_min_bond() * 2u32.into();
1138		let (depositor, _pool_account) = create_pool_account::<T>(0, deposit_amount, None);
1139		let depositor_lookup = T::Lookup::unlookup(depositor.clone());
1140
1141		// migrate pool to transfer stake.
1142		let _ = migrate_to_transfer_stake::<T>(1);
1143
1144		// Now migrate pool to delegate stake keeping delegators unmigrated.
1145		assert!(Pools::<T>::migrate_pool_to_delegate_stake(
1146			RuntimeOrigin::Signed(depositor.clone()).into(),
1147			1u32.into(),
1148		)
1149		.is_ok(),);
1150
1151		// delegation does not exist.
1152		assert!(
1153			T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())).is_none()
1154		);
1155		// contribution exists in the pool.
1156		assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount);
1157
1158		whitelist_account!(depositor);
1159
1160		#[block]
1161		{
1162			assert!(Pools::<T>::migrate_delegation(
1163				RuntimeOrigin::Signed(depositor.clone()).into(),
1164				depositor_lookup.clone(),
1165			)
1166			.is_ok(),);
1167		}
1168		// verify balances once more.
1169		assert!(
1170			T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
1171				Some(deposit_amount),
1172		);
1173		assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount);
1174	}
1175
1176	impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Runtime);
1177}