1use 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};
45use 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
73fn 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
95fn 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 return;
138 }
139 let pool_acc = Pools::<T>::generate_bonded_account(pool_id);
140 T::StakeAdapter::remove_as_agent(Pool::from(pool_acc.clone()));
142
143 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 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 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 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 CurrencyOf::<T>::set_total_issuance(Zero::zero());
204
205 let validator =
207 create_validator::<T>(0, CurrencyOf::<T>::minimum_balance() * 1000u32.into());
208
209 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 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 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);
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 T::StakeAdapter::unbond(Pool::from(self.origin1.clone()), amount)
265 .expect("the pool was created in `Self::new`.");
266
267 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 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 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 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 let _ = CurrencyOf::<T>::mint_into(
332 &scenario.creator1,
333 extra + CurrencyOf::<T>::minimum_balance(),
334 );
335
336 #[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 let _ = Pools::<T>::set_claim_permission(
357 RuntimeOrigin::Signed(scenario.creator1.clone()).into(),
358 ClaimPermission::PermissionlessAll,
359 );
360
361 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 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 CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
392
393 let _ = Pools::<T>::set_claim_permission(
396 RuntimeOrigin::Signed(depositor.clone()).into(),
397 ClaimPermission::PermissionlessAll,
398 );
399
400 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 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 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 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 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 Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone())
466 .unwrap();
467
468 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 <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 assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
487 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 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 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 <T::StakeAdapter as StakeStrategy>::CoreStaking::set_era(0);
514 Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone())
515 .unwrap();
516
517 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 <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 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 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 <T::StakeAdapter as StakeStrategy>::CoreStaking::set_era(0);
559 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 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 <T::StakeAdapter as StakeStrategy>::CoreStaking::set_era(EraIndex::max_value());
584
585 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 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 assert_eq!(
609 CurrencyOf::<T>::balance(&depositor),
610 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 CurrencyOf::<T>::set_balance(
625 &depositor,
626 min_create_bond + CurrencyOf::<T>::minimum_balance() * 2u32.into(),
627 );
628 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 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 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 let min_create_bond = Pools::<T>::depositor_min_bond();
711 let _ = create_pool_account::<T>(0, min_create_bond, None);
713 BondedPools::<T>::mutate(&1, |maybe_pool| {
714 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 let (depositor, _pool_account) =
733 create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
734
735 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 let (depositor, pool_account) =
797 create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
798
799 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 let (depositor, _pool_account) =
820 create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
821 Pools::<T>::set_commission_max(
823 RuntimeOrigin::Signed(depositor.clone()).into(),
824 1u32.into(),
825 Perbill::from_percent(50),
826 )
827 .unwrap();
828 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 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 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 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 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 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 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 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 let _ = Pools::<T>::claim_payout(RuntimeOrigin::Signed(claimer.clone()).into());
984 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 let (depositor, _) =
1006 create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
1007
1008 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 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 assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount);
1031 assert!(
1033 T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
1034 Some(deposit_amount),
1035 );
1036
1037 let slash_amount: u128 = deposit_amount.into() / 2;
1040
1041 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 assert_eq!(
1052 PoolMembers::<T>::get(&depositor).unwrap().total_balance(),
1053 deposit_amount / 2u32.into()
1054 );
1055 assert!(
1057 T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
1058 Some(deposit_amount),
1059 );
1060
1061 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 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 let deposit_amount = Pools::<T>::depositor_min_bond() * 10u32.into();
1103 let (_depositor, pool_account) = create_pool_account::<T>(0, deposit_amount, None);
1105
1106 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 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 for i in 0..T::MaxUnbonding::get() {
1129 <T::StakeAdapter as StakeStrategy>::CoreStaking::set_era(i + 2); 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 #[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 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 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 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 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 let _ = migrate_to_transfer_stake::<T>(1);
1185
1186 assert!(Pools::<T>::migrate_pool_to_delegate_stake(
1188 RuntimeOrigin::Signed(depositor.clone()).into(),
1189 1u32.into(),
1190 )
1191 .is_ok(),);
1192
1193 assert!(
1195 T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())).is_none()
1196 );
1197 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 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}