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