1use super::*;
21use crate::{
22 asset,
23 session_rotation::{Eras, Rotator},
24 Pallet as Staking,
25};
26use alloc::collections::BTreeMap;
27pub use frame_benchmarking::{
28 impl_benchmark_test_suite, v2::*, whitelist_account, whitelisted_caller, BenchmarkError,
29};
30use frame_election_provider_support::SortedListProvider;
31use frame_support::{
32 assert_ok,
33 pallet_prelude::*,
34 storage::bounded_vec::BoundedVec,
35 traits::{fungible::Inspect, TryCollect},
36};
37use frame_system::RawOrigin;
38use pallet_staking_async_rc_client as rc_client;
39use sp_runtime::{
40 traits::{Bounded, One, StaticLookup, Zero},
41 Perbill, Percent, Saturating,
42};
43use sp_staking::currency_to_vote::CurrencyToVote;
44use testing_utils::*;
45
46const SEED: u32 = 0;
47
48pub(crate) fn create_validator_with_nominators<T: Config>(
53 n: u32,
54 upper_bound: u32,
55 dead_controller: bool,
56 unique_controller: bool,
57 destination: RewardDestination<T::AccountId>,
58) -> Result<(T::AccountId, Vec<(T::AccountId, T::AccountId)>, EraIndex), &'static str> {
59 clear_validators_and_nominators::<T>();
62 let mut points_total = 0;
63 let mut points_individual = Vec::new();
64
65 let (v_stash, v_controller) = if unique_controller {
66 create_unique_stash_controller::<T>(0, 100, destination.clone(), false)?
67 } else {
68 create_stash_controller::<T>(0, 100, destination.clone())?
69 };
70
71 let validator_prefs =
72 ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
73 Staking::<T>::validate(RawOrigin::Signed(v_controller).into(), validator_prefs)?;
74 let stash_lookup = T::Lookup::unlookup(v_stash.clone());
75
76 points_total += 10;
77 points_individual.push((v_stash.clone(), 10));
78
79 let original_nominator_count = Nominators::<T>::count();
80 let mut nominators = Vec::new();
81
82 for i in 0..upper_bound {
84 let (n_stash, n_controller) = if !dead_controller {
85 create_stash_controller::<T>(u32::MAX - i, 100, destination.clone())?
86 } else {
87 create_unique_stash_controller::<T>(u32::MAX - i, 100, destination.clone(), true)?
88 };
89 if i < n {
90 Staking::<T>::nominate(
91 RawOrigin::Signed(n_controller.clone()).into(),
92 vec![stash_lookup.clone()],
93 )?;
94 nominators.push((n_stash, n_controller));
95 }
96 }
97
98 ValidatorCount::<T>::put(1);
99
100 let new_validators = Rotator::<T>::legacy_insta_plan_era();
102 let planned_era = CurrentEra::<T>::get().unwrap_or_default();
103
104 assert_eq!(new_validators.len(), 1, "New validators is not 1");
105 assert_eq!(new_validators[0], v_stash, "Our validator was not selected");
106 assert_ne!(Validators::<T>::count(), 0, "New validators count wrong");
107 assert_eq!(
108 Nominators::<T>::count(),
109 original_nominator_count + nominators.len() as u32,
110 "New nominators count wrong"
111 );
112
113 let reward = EraRewardPoints::<T> {
115 total: points_total,
116 individual: points_individual.into_iter().try_collect()?,
117 };
118
119 ErasRewardPoints::<T>::insert(planned_era, reward);
120
121 let total_payout = asset::existential_deposit::<T>()
123 .saturating_mul(upper_bound.into())
124 .saturating_mul(1000u32.into());
125 <ErasValidatorReward<T>>::insert(planned_era, total_payout);
126
127 Ok((v_stash, nominators, planned_era))
128}
129
130struct ListScenario<T: Config> {
131 origin_stash1: T::AccountId,
133 origin_controller1: T::AccountId,
135 dest_weight: BalanceOf<T>,
136}
137
138impl<T: Config> ListScenario<T> {
139 fn new(origin_weight: BalanceOf<T>, is_increase: bool) -> Result<Self, &'static str> {
154 ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0");
155
156 let i = asset::burn::<T>(asset::total_issuance::<T>());
158 core::mem::forget(i);
159
160 let validator_account = account("random_validator", 0, SEED);
162 let validator_stake = asset::existential_deposit::<T>() * 1000u32.into();
163 asset::set_stakeable_balance::<T>(&validator_account, validator_stake);
164 assert_ok!(Staking::<T>::bond(
165 RawOrigin::Signed(validator_account.clone()).into(),
166 validator_stake / 2u32.into(),
167 RewardDestination::Staked
168 ));
169 assert_ok!(Staking::<T>::validate(
170 RawOrigin::Signed(validator_account.clone()).into(),
171 Default::default()
172 ));
173
174 let (origin_stash1, origin_controller1) = create_stash_controller_with_balance::<T>(
176 USER_SEED + 2,
177 origin_weight,
178 RewardDestination::Staked,
179 )?;
180 Staking::<T>::nominate(
181 RawOrigin::Signed(origin_controller1.clone()).into(),
182 vec![T::Lookup::unlookup(validator_account.clone())],
184 )?;
185
186 let (_origin_stash2, origin_controller2) = create_stash_controller_with_balance::<T>(
187 USER_SEED + 3,
188 origin_weight,
189 RewardDestination::Staked,
190 )?;
191 Staking::<T>::nominate(
192 RawOrigin::Signed(origin_controller2).into(),
193 vec![T::Lookup::unlookup(validator_account.clone())],
194 )?;
195
196 let dest_weight_as_vote =
198 T::VoterList::score_update_worst_case(&origin_stash1, is_increase);
199
200 let total_issuance = asset::total_issuance::<T>();
201
202 let dest_weight =
203 T::CurrencyToVote::to_currency(dest_weight_as_vote as u128, total_issuance);
204
205 let (_dest_stash1, dest_controller1) = create_stash_controller_with_balance::<T>(
207 USER_SEED + 1,
208 dest_weight,
209 RewardDestination::Staked,
210 )?;
211 Staking::<T>::nominate(
212 RawOrigin::Signed(dest_controller1).into(),
213 vec![T::Lookup::unlookup(validator_account)],
214 )?;
215
216 Ok(ListScenario { origin_stash1, origin_controller1, dest_weight })
217 }
218}
219
220const USER_SEED: u32 = 999666;
221
222#[benchmarks]
223mod benchmarks {
224 use super::*;
225 use alloc::format;
226
227 #[benchmark]
228 fn bond() {
229 let stash = create_funded_user::<T>("stash", USER_SEED, 100);
230 let reward_destination = RewardDestination::Staked;
231 let amount = asset::existential_deposit::<T>() * 10u32.into();
232 whitelist_account!(stash);
233
234 #[extrinsic_call]
235 _(RawOrigin::Signed(stash.clone()), amount, reward_destination);
236
237 assert!(Bonded::<T>::contains_key(stash.clone()));
238 assert!(Ledger::<T>::contains_key(stash));
239 }
240
241 #[benchmark]
242 fn bond_extra() -> Result<(), BenchmarkError> {
243 clear_validators_and_nominators::<T>();
245
246 let origin_weight = Staking::<T>::min_nominator_bond();
247
248 let scenario = ListScenario::<T>::new(origin_weight, true)?;
252
253 let max_additional = scenario.dest_weight - origin_weight;
254
255 let stash = scenario.origin_stash1.clone();
256 let controller = scenario.origin_controller1;
257 let original_bonded: BalanceOf<T> = Ledger::<T>::get(&controller)
258 .map(|l| l.active)
259 .ok_or("ledger not created after")?;
260
261 let _ = asset::mint_into_existing::<T>(
262 &stash,
263 max_additional + asset::existential_deposit::<T>(),
264 )
265 .unwrap();
266
267 whitelist_account!(stash);
268
269 #[extrinsic_call]
270 _(RawOrigin::Signed(stash), max_additional);
271
272 let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created after")?;
273 let new_bonded: BalanceOf<T> = ledger.active;
274 assert!(original_bonded < new_bonded);
275
276 Ok(())
277 }
278
279 #[benchmark]
280 fn unbond() -> Result<(), BenchmarkError> {
281 clear_validators_and_nominators::<T>();
283
284 let origin_weight = BalanceOf::<T>::try_from(952_994_955_240_703u128)
287 .map_err(|_| "balance expected to be a u128")
288 .unwrap();
289 let scenario = ListScenario::<T>::new(origin_weight, false)?;
290
291 let controller = scenario.origin_controller1.clone();
292 let amount = origin_weight - scenario.dest_weight;
293 let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created before")?;
294 let original_bonded: BalanceOf<T> = ledger.active;
295
296 whitelist_account!(controller);
297
298 #[extrinsic_call]
299 _(RawOrigin::Signed(controller.clone()), amount);
300
301 let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created after")?;
302 let new_bonded: BalanceOf<T> = ledger.active;
303 assert!(original_bonded > new_bonded);
304
305 Ok(())
306 }
307
308 #[benchmark]
309 fn withdraw_unbonded_update() -> Result<(), BenchmarkError> {
311 let (_, controller) = create_stash_controller::<T>(0, 100, RewardDestination::Staked)?;
312 let amount = asset::existential_deposit::<T>() * 5u32.into(); Staking::<T>::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?;
314 set_active_era::<T>(EraIndex::max_value());
315 let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created before")?;
316 let original_total: BalanceOf<T> = ledger.total;
317 whitelist_account!(controller);
318
319 #[extrinsic_call]
320 withdraw_unbonded(RawOrigin::Signed(controller.clone()), 0);
321
322 let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created after")?;
323 let new_total: BalanceOf<T> = ledger.total;
324 assert!(original_total > new_total);
325
326 Ok(())
327 }
328
329 #[benchmark]
330 fn withdraw_unbonded_kill() -> Result<(), BenchmarkError> {
332 clear_validators_and_nominators::<T>();
334
335 let origin_weight = Staking::<T>::min_nominator_bond();
336
337 let scenario = ListScenario::<T>::new(origin_weight, true)?;
340 let controller = scenario.origin_controller1.clone();
341 let stash = scenario.origin_stash1;
342 assert!(T::VoterList::contains(&stash));
343
344 let ed = asset::existential_deposit::<T>();
345 let mut ledger = Ledger::<T>::get(&controller).unwrap();
346 ledger.active = ed - One::one();
347 Ledger::<T>::insert(&controller, ledger);
348 set_active_era::<T>(EraIndex::max_value());
349
350 whitelist_account!(controller);
351
352 #[extrinsic_call]
353 withdraw_unbonded(RawOrigin::Signed(controller.clone()), 0);
354
355 assert!(!Ledger::<T>::contains_key(controller));
356 assert!(!T::VoterList::contains(&stash));
357
358 Ok(())
359 }
360
361 #[benchmark]
362 fn validate() -> Result<(), BenchmarkError> {
363 let (stash, controller) = create_stash_controller::<T>(
364 MaxNominationsOf::<T>::get() - 1,
365 100,
366 RewardDestination::Staked,
367 )?;
368 assert!(!T::VoterList::contains(&stash));
370
371 let prefs = ValidatorPrefs::default();
372 whitelist_account!(controller);
373
374 #[extrinsic_call]
375 _(RawOrigin::Signed(controller), prefs);
376
377 assert!(Validators::<T>::contains_key(&stash));
378 assert!(T::VoterList::contains(&stash));
379
380 Ok(())
381 }
382
383 #[benchmark]
384 fn kick(
385 k: Linear<1, 128>,
390 ) -> Result<(), BenchmarkError> {
391 let rest_of_validators =
394 create_validators_with_seed::<T>(MaxNominationsOf::<T>::get() - 1, 100, 415)?;
395
396 let (stash, controller) = create_stash_controller::<T>(
398 MaxNominationsOf::<T>::get() - 1,
399 100,
400 RewardDestination::Staked,
401 )?;
402 let stash_lookup = T::Lookup::unlookup(stash.clone());
403
404 Staking::<T>::validate(RawOrigin::Signed(controller.clone()).into(), Default::default())?;
406
407 let mut nominator_stashes = Vec::with_capacity(k as usize);
410 for i in 0..k {
411 let (n_stash, n_controller) = create_stash_controller::<T>(
413 MaxNominationsOf::<T>::get() + i,
414 100,
415 RewardDestination::Staked,
416 )?;
417
418 let mut nominations = rest_of_validators.clone();
420 nominations.insert(i as usize % (nominations.len() + 1), stash_lookup.clone());
423 Staking::<T>::nominate(RawOrigin::Signed(n_controller.clone()).into(), nominations)?;
425
426 nominator_stashes.push(n_stash);
427 }
428
429 for n in nominator_stashes.iter() {
431 assert!(Nominators::<T>::get(n).unwrap().targets.contains(&stash));
432 }
433
434 let kicks = nominator_stashes
436 .iter()
437 .map(|n| T::Lookup::unlookup(n.clone()))
438 .collect::<Vec<_>>();
439
440 whitelist_account!(controller);
441
442 #[extrinsic_call]
443 _(RawOrigin::Signed(controller), kicks);
444
445 for n in nominator_stashes.iter() {
447 assert!(!Nominators::<T>::get(n).unwrap().targets.contains(&stash));
448 }
449
450 Ok(())
451 }
452
453 #[benchmark]
454 fn nominate(n: Linear<1, { MaxNominationsOf::<T>::get() }>) -> Result<(), BenchmarkError> {
456 clear_validators_and_nominators::<T>();
458
459 let origin_weight = Staking::<T>::min_nominator_bond();
460
461 ListScenario::<T>::new(origin_weight, true)?;
464 let (stash, controller) = create_stash_controller_with_balance::<T>(
465 SEED + MaxNominationsOf::<T>::get() + 1, origin_weight,
468 RewardDestination::Staked,
469 )
470 .unwrap();
471
472 assert!(!Nominators::<T>::contains_key(&stash));
473 assert!(!T::VoterList::contains(&stash));
474
475 let validators = create_validators::<T>(n, 100).unwrap();
476 whitelist_account!(controller);
477
478 #[extrinsic_call]
479 _(RawOrigin::Signed(controller), validators);
480
481 assert!(Nominators::<T>::contains_key(&stash));
482 assert!(T::VoterList::contains(&stash));
483
484 Ok(())
485 }
486
487 #[benchmark]
488 fn chill() -> Result<(), BenchmarkError> {
489 clear_validators_and_nominators::<T>();
491
492 let origin_weight = Staking::<T>::min_nominator_bond();
493
494 let scenario = ListScenario::<T>::new(origin_weight, true)?;
497 let controller = scenario.origin_controller1.clone();
498 let stash = scenario.origin_stash1;
499 assert!(T::VoterList::contains(&stash));
500
501 whitelist_account!(controller);
502
503 #[extrinsic_call]
504 _(RawOrigin::Signed(controller));
505
506 assert!(!T::VoterList::contains(&stash));
507
508 Ok(())
509 }
510
511 #[benchmark]
512 fn set_payee() -> Result<(), BenchmarkError> {
513 let (stash, controller) =
514 create_stash_controller::<T>(USER_SEED, 100, RewardDestination::Staked)?;
515 assert_eq!(Payee::<T>::get(&stash), Some(RewardDestination::Staked));
516 whitelist_account!(controller);
517
518 #[extrinsic_call]
519 _(RawOrigin::Signed(controller.clone()), RewardDestination::Account(controller.clone()));
520
521 assert_eq!(Payee::<T>::get(&stash), Some(RewardDestination::Account(controller)));
522
523 Ok(())
524 }
525
526 #[benchmark]
527 fn update_payee() -> Result<(), BenchmarkError> {
528 let (stash, controller) =
529 create_stash_controller::<T>(USER_SEED, 100, RewardDestination::Staked)?;
530 Payee::<T>::insert(&stash, {
531 #[allow(deprecated)]
532 RewardDestination::Controller
533 });
534 whitelist_account!(controller);
535
536 #[extrinsic_call]
537 _(RawOrigin::Signed(controller.clone()), controller.clone());
538
539 assert_eq!(Payee::<T>::get(&stash), Some(RewardDestination::Account(controller)));
540
541 Ok(())
542 }
543
544 #[benchmark]
545 fn set_controller() -> Result<(), BenchmarkError> {
546 let (stash, ctlr) =
547 create_unique_stash_controller::<T>(9000, 100, RewardDestination::Staked, false)?;
548 assert!(!Ledger::<T>::contains_key(&stash));
550 assert!(Ledger::<T>::contains_key(&ctlr));
551 assert_eq!(Bonded::<T>::get(&stash), Some(ctlr.clone()));
552
553 whitelist_account!(stash);
554
555 #[extrinsic_call]
556 _(RawOrigin::Signed(stash.clone()));
557
558 assert!(Ledger::<T>::contains_key(&stash));
559
560 Ok(())
561 }
562
563 #[benchmark]
564 fn set_validator_count() {
565 let validator_count = T::MaxValidatorSet::get() - 1;
566
567 #[extrinsic_call]
568 _(RawOrigin::Root, validator_count);
569
570 assert_eq!(ValidatorCount::<T>::get(), validator_count);
571 }
572
573 #[benchmark]
574 fn force_no_eras() {
575 #[extrinsic_call]
576 _(RawOrigin::Root);
577
578 assert_eq!(ForceEra::<T>::get(), Forcing::ForceNone);
579 }
580
581 #[benchmark]
582 fn force_new_era() {
583 #[extrinsic_call]
584 _(RawOrigin::Root);
585
586 assert_eq!(ForceEra::<T>::get(), Forcing::ForceNew);
587 }
588
589 #[benchmark]
590 fn force_new_era_always() {
591 #[extrinsic_call]
592 _(RawOrigin::Root);
593
594 assert_eq!(ForceEra::<T>::get(), Forcing::ForceAlways);
595 }
596
597 #[benchmark]
598 fn deprecate_controller_batch(
599 u: Linear<0, { T::MaxControllersInDeprecationBatch::get() }>,
602 ) -> Result<(), BenchmarkError> {
603 let mut controllers: Vec<_> = vec![];
604 let mut stashes: Vec<_> = vec![];
605 for i in 0..u as u32 {
606 let (stash, controller) =
607 create_unique_stash_controller::<T>(i, 100, RewardDestination::Staked, false)?;
608 controllers.push(controller);
609 stashes.push(stash);
610 }
611 let bounded_controllers: BoundedVec<_, T::MaxControllersInDeprecationBatch> =
612 BoundedVec::try_from(controllers.clone()).unwrap();
613
614 #[extrinsic_call]
615 _(RawOrigin::Root, bounded_controllers);
616
617 for i in 0..u as u32 {
618 let stash = &stashes[i as usize];
619 let controller = &controllers[i as usize];
620 assert_eq!(Ledger::<T>::get(controller), None);
622 assert_eq!(Bonded::<T>::get(stash), Some(stash.clone()));
624 assert_eq!(Ledger::<T>::get(stash).unwrap().stash, *stash);
626 }
627
628 Ok(())
629 }
630
631 #[benchmark]
632 fn force_unstake() -> Result<(), BenchmarkError> {
633 clear_validators_and_nominators::<T>();
635
636 let origin_weight = Staking::<T>::min_nominator_bond();
637
638 let scenario = ListScenario::<T>::new(origin_weight, true)?;
641 let controller = scenario.origin_controller1.clone();
642 let stash = scenario.origin_stash1;
643 assert!(T::VoterList::contains(&stash));
644
645 #[extrinsic_call]
646 _(RawOrigin::Root, stash.clone(), 0);
647
648 assert!(!Ledger::<T>::contains_key(&controller));
649 assert!(!T::VoterList::contains(&stash));
650
651 Ok(())
652 }
653
654 #[benchmark]
655 fn cancel_deferred_slash(s: Linear<1, { T::MaxValidatorSet::get() }>) {
656 let era = EraIndex::one();
657
658 let validators: Vec<_> = (0..s)
660 .map(|i| {
661 let validator: T::AccountId = account("validator", i, SEED);
662
663 let slash_key = (validator.clone(), Perbill::from_percent(10), 0);
665 let unapplied_slash = UnappliedSlash::<T> {
666 validator: validator.clone(),
667 own: Zero::zero(),
668 others: WeakBoundedVec::default(),
669 reporter: Default::default(),
670 payout: Zero::zero(),
671 };
672 UnappliedSlashes::<T>::insert(era, slash_key, unapplied_slash);
673
674 validator
675 })
676 .collect();
677
678 let validator_slashes: Vec<_> =
680 validators.into_iter().map(|v| (v, Perbill::from_percent(10))).collect();
681
682 #[extrinsic_call]
683 _(RawOrigin::Root, era, validator_slashes.clone());
684
685 let cancelled_slashes = CancelledSlashes::<T>::get(era);
687 assert_eq!(cancelled_slashes.len(), s as usize);
688 }
689
690 #[benchmark]
691 fn payout_stakers_alive_staked(
692 n: Linear<0, { T::MaxExposurePageSize::get() as u32 }>,
693 ) -> Result<(), BenchmarkError> {
694 let (validator, nominators, current_era) = create_validator_with_nominators::<T>(
695 n,
696 T::MaxExposurePageSize::get() as u32,
697 false,
698 true,
699 RewardDestination::Staked,
700 )?;
701
702 <ErasValidatorPrefs<T>>::insert(
704 current_era,
705 validator.clone(),
706 Validators::<T>::get(&validator),
707 );
708
709 let caller = whitelisted_caller();
710 let balance_before = asset::stakeable_balance::<T>(&validator);
711 let mut nominator_balances_before = Vec::new();
712 for (stash, _) in &nominators {
713 let balance = asset::stakeable_balance::<T>(stash);
714 nominator_balances_before.push(balance);
715 }
716
717 #[extrinsic_call]
718 payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era);
719
720 let balance_after = asset::stakeable_balance::<T>(&validator);
721 ensure!(
722 balance_before < balance_after,
723 "Balance of validator stash should have increased after payout.",
724 );
725 for ((stash, _), balance_before) in nominators.iter().zip(nominator_balances_before.iter())
726 {
727 let balance_after = asset::stakeable_balance::<T>(stash);
728 ensure!(
729 balance_before < &balance_after,
730 "Balance of nominator stash should have increased after payout.",
731 );
732 }
733
734 Ok(())
735 }
736
737 #[benchmark]
738 fn rebond(l: Linear<1, { T::MaxUnlockingChunks::get() as u32 }>) -> Result<(), BenchmarkError> {
739 clear_validators_and_nominators::<T>();
741
742 let origin_weight = Pallet::<T>::min_nominator_bond()
743 .max(100u32.into());
745
746 let scenario = ListScenario::<T>::new(origin_weight, true)?;
748 let dest_weight = scenario.dest_weight;
749
750 let rebond_amount = dest_weight - origin_weight;
752
753 let value = rebond_amount / l.into();
755 assert_ne!(value, Zero::zero());
757 assert!(value * l.into() + origin_weight > origin_weight);
759 assert!(value * l.into() + origin_weight <= dest_weight);
760 let unlock_chunk = UnlockChunk::<BalanceOf<T>> { value, era: EraIndex::zero() };
761
762 let controller = scenario.origin_controller1;
763 let mut staking_ledger = Ledger::<T>::get(controller.clone()).unwrap();
764
765 for _ in 0..l {
766 staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap()
767 }
768 Ledger::<T>::insert(controller.clone(), staking_ledger.clone());
769 let original_bonded: BalanceOf<T> = staking_ledger.active;
770
771 whitelist_account!(controller);
772
773 #[extrinsic_call]
774 _(RawOrigin::Signed(controller.clone()), rebond_amount);
775
776 let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created after")?;
777 let new_bonded: BalanceOf<T> = ledger.active;
778 assert!(original_bonded < new_bonded);
779
780 Ok(())
781 }
782
783 #[benchmark]
784 fn reap_stash() -> Result<(), BenchmarkError> {
785 clear_validators_and_nominators::<T>();
787
788 let origin_weight = Staking::<T>::min_nominator_bond();
789
790 let scenario = ListScenario::<T>::new(origin_weight, true)?;
793 let controller = scenario.origin_controller1.clone();
794 let stash = scenario.origin_stash1;
795
796 let l =
797 StakingLedger::<T>::new(stash.clone(), asset::existential_deposit::<T>() - One::one());
798 Ledger::<T>::insert(&controller, l);
799
800 assert!(Bonded::<T>::contains_key(&stash));
801 assert!(T::VoterList::contains(&stash));
802
803 whitelist_account!(controller);
804
805 #[extrinsic_call]
806 _(RawOrigin::Signed(controller), stash.clone(), 0);
807
808 assert!(!Bonded::<T>::contains_key(&stash));
809 assert!(!T::VoterList::contains(&stash));
810
811 Ok(())
812 }
813
814 #[benchmark]
815 fn set_staking_configs_all_set() {
816 #[extrinsic_call]
817 set_staking_configs(
818 RawOrigin::Root,
819 ConfigOp::Set(BalanceOf::<T>::max_value()),
820 ConfigOp::Set(BalanceOf::<T>::max_value()),
821 ConfigOp::Set(u32::MAX),
822 ConfigOp::Set(u32::MAX),
823 ConfigOp::Set(Percent::max_value()),
824 ConfigOp::Set(Perbill::max_value()),
825 ConfigOp::Set(Percent::max_value()),
826 );
827
828 assert_eq!(MinNominatorBond::<T>::get(), BalanceOf::<T>::max_value());
829 assert_eq!(MinValidatorBond::<T>::get(), BalanceOf::<T>::max_value());
830 assert_eq!(MaxNominatorsCount::<T>::get(), Some(u32::MAX));
831 assert_eq!(MaxValidatorsCount::<T>::get(), Some(u32::MAX));
832 assert_eq!(ChillThreshold::<T>::get(), Some(Percent::from_percent(100)));
833 assert_eq!(MinCommission::<T>::get(), Perbill::from_percent(100));
834 assert_eq!(MaxStakedRewards::<T>::get(), Some(Percent::from_percent(100)));
835 }
836
837 #[benchmark]
838 fn set_staking_configs_all_remove() {
839 #[extrinsic_call]
840 set_staking_configs(
841 RawOrigin::Root,
842 ConfigOp::Remove,
843 ConfigOp::Remove,
844 ConfigOp::Remove,
845 ConfigOp::Remove,
846 ConfigOp::Remove,
847 ConfigOp::Remove,
848 ConfigOp::Remove,
849 );
850
851 assert!(!MinNominatorBond::<T>::exists());
852 assert!(!MinValidatorBond::<T>::exists());
853 assert!(!MaxNominatorsCount::<T>::exists());
854 assert!(!MaxValidatorsCount::<T>::exists());
855 assert!(!ChillThreshold::<T>::exists());
856 assert!(!MinCommission::<T>::exists());
857 assert!(!MaxStakedRewards::<T>::exists());
858 }
859
860 #[benchmark]
861 fn chill_other() -> Result<(), BenchmarkError> {
862 clear_validators_and_nominators::<T>();
864
865 let origin_weight = Staking::<T>::min_nominator_bond();
866
867 let scenario = ListScenario::<T>::new(origin_weight, true)?;
870 let stash = scenario.origin_stash1;
871 assert!(T::VoterList::contains(&stash));
872
873 Staking::<T>::set_staking_configs(
874 RawOrigin::Root.into(),
875 ConfigOp::Set(BalanceOf::<T>::max_value()),
876 ConfigOp::Set(BalanceOf::<T>::max_value()),
877 ConfigOp::Set(0),
878 ConfigOp::Set(0),
879 ConfigOp::Set(Percent::from_percent(0)),
880 ConfigOp::Set(Zero::zero()),
881 ConfigOp::Noop,
882 )?;
883
884 let caller = whitelisted_caller();
885
886 #[extrinsic_call]
887 _(RawOrigin::Signed(caller), stash.clone());
888
889 assert!(!T::VoterList::contains(&stash));
890
891 Ok(())
892 }
893
894 #[benchmark]
895 fn force_apply_min_commission() -> Result<(), BenchmarkError> {
896 clear_validators_and_nominators::<T>();
898
899 let (stash, controller) = create_stash_controller::<T>(1, 1, RewardDestination::Staked)?;
901 let validator_prefs =
902 ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
903 Staking::<T>::validate(RawOrigin::Signed(controller).into(), validator_prefs)?;
904
905 assert_eq!(
907 Validators::<T>::get(&stash),
908 ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }
909 );
910
911 MinCommission::<T>::set(Perbill::from_percent(75));
913 let caller = whitelisted_caller();
914
915 #[extrinsic_call]
916 _(RawOrigin::Signed(caller), stash.clone());
917
918 assert_eq!(
920 Validators::<T>::get(&stash),
921 ValidatorPrefs { commission: Perbill::from_percent(75), ..Default::default() }
922 );
923
924 Ok(())
925 }
926
927 #[benchmark]
928 fn set_min_commission() {
929 let min_commission = Perbill::max_value();
930
931 #[extrinsic_call]
932 _(RawOrigin::Root, min_commission);
933
934 assert_eq!(MinCommission::<T>::get(), Perbill::from_percent(100));
935 }
936
937 #[benchmark]
938 fn restore_ledger() -> Result<(), BenchmarkError> {
939 let (stash, controller) = create_stash_controller::<T>(0, 100, RewardDestination::Staked)?;
940 Ledger::<T>::remove(controller);
942
943 #[extrinsic_call]
944 _(RawOrigin::Root, stash.clone(), None, None, None);
945
946 assert_eq!(Staking::<T>::inspect_bond_state(&stash), Ok(LedgerIntegrityState::Ok));
947
948 Ok(())
949 }
950
951 #[benchmark]
952 fn migrate_currency() -> Result<(), BenchmarkError> {
953 let (stash, _ctrl) =
954 create_stash_controller::<T>(USER_SEED, 100, RewardDestination::Staked)?;
955 let stake = asset::staked::<T>(&stash);
956 migrate_to_old_currency::<T>(stash.clone());
957 assert!(asset::staked::<T>(&stash).is_zero());
959 whitelist_account!(stash);
960
961 #[extrinsic_call]
962 _(RawOrigin::Signed(stash.clone()), stash.clone());
963
964 assert_eq!(asset::staked::<T>(&stash), stake);
965 Ok(())
966 }
967
968 #[benchmark]
969 fn apply_slash() -> Result<(), BenchmarkError> {
970 let era = EraIndex::one();
971 ActiveEra::<T>::put(ActiveEraInfo { index: era, start: None });
972 let (validator, nominators, _current_era) = create_validator_with_nominators::<T>(
973 T::MaxExposurePageSize::get() as u32,
974 T::MaxExposurePageSize::get() as u32,
975 false,
976 true,
977 RewardDestination::Staked,
978 )?;
979 let slash_fraction = Perbill::from_percent(10);
980 let page_index = 0;
981 let slashed_balance = BalanceOf::<T>::from(10u32);
982
983 let slash_key = (validator.clone(), slash_fraction, page_index);
984 let slashed_nominators =
985 nominators.iter().map(|(n, _)| (n.clone(), slashed_balance)).collect::<Vec<_>>();
986
987 let unapplied_slash = UnappliedSlash::<T> {
988 validator: validator.clone(),
989 own: slashed_balance,
990 others: WeakBoundedVec::force_from(slashed_nominators, None),
991 reporter: Default::default(),
992 payout: Zero::zero(),
993 };
994
995 UnappliedSlashes::<T>::insert(era, slash_key.clone(), unapplied_slash);
997
998 #[extrinsic_call]
999 _(RawOrigin::Signed(validator.clone()), era, slash_key.clone());
1000
1001 assert!(UnappliedSlashes::<T>::get(era, &slash_key).is_none());
1003
1004 Ok(())
1005 }
1006
1007 #[benchmark]
1008 fn process_offence_queue() -> Result<(), BenchmarkError> {
1009 #[cfg(test)]
1012 crate::mock::SlashDeferDuration::set(77);
1013
1014 let all_validators = crate::testing_utils::create_validators_with_nominators_for_era::<T>(
1016 ValidatorCount::<T>::get().max(1),
1018 2 * T::MaxExposurePageSize::get(),
1020 16,
1021 false,
1022 Some(1),
1023 )?;
1024 let offender =
1025 T::Lookup::lookup(all_validators.first().cloned().expect("must exist")).unwrap();
1026
1027 let _new_validators = Rotator::<T>::legacy_insta_plan_era();
1029 Rotator::<T>::start_era(
1031 crate::ActiveEraInfo { index: Rotator::<T>::planned_era() - 1, start: Some(1) },
1032 42, 2, );
1035
1036 let offender_exposure =
1038 Eras::<T>::get_full_exposure(Rotator::<T>::planned_era(), &offender);
1039 ensure!(
1040 offender_exposure.others.len() as u32 == 2 * T::MaxExposurePageSize::get(),
1041 "exposure not created"
1042 );
1043
1044 let slash_session = 42;
1046 let offences = vec![rc_client::Offence {
1047 offender: offender.clone(),
1048 reporters: Default::default(),
1049 slash_fraction: Perbill::from_percent(50),
1050 }];
1051 <crate::Pallet<T> as rc_client::AHStakingInterface>::on_new_offences(
1052 slash_session,
1053 offences,
1054 );
1055
1056 ensure!(
1058 ValidatorSlashInEra::<T>::contains_key(Rotator::<T>::active_era(), offender),
1059 "offence not submitted"
1060 );
1061 ensure!(
1062 OffenceQueueEras::<T>::get().unwrap_or_default() == vec![Rotator::<T>::active_era()],
1063 "offence should be queued"
1064 );
1065
1066 #[block]
1067 {
1068 slashing::process_offence::<T>();
1069 }
1070
1071 ensure!(OffenceQueueEras::<T>::get().is_none(), "offence should not be queued");
1072
1073 Ok(())
1074 }
1075
1076 #[benchmark]
1077 fn rc_on_offence(
1078 v: Linear<2, { T::MaxValidatorSet::get() / 2 }>,
1079 ) -> Result<(), BenchmarkError> {
1080 let initial_era = Rotator::<T>::planned_era();
1081 let _ = crate::testing_utils::create_validators_with_nominators_for_era::<T>(
1082 2 * v,
1083 1000,
1085 16,
1086 false,
1087 None,
1088 )?;
1089
1090 let new_validators = Rotator::<T>::legacy_insta_plan_era();
1092 ensure!(Rotator::<T>::planned_era() == initial_era + 1, "era should be incremented");
1093 Rotator::<T>::start_era(
1095 crate::ActiveEraInfo { index: initial_era, start: Some(1) },
1096 42, 2, );
1099
1100 ensure!(Rotator::<T>::active_era_start_session_index() == 42, "BondedEra not set");
1102
1103 let to_slash_count = new_validators.len() / 2;
1105 let to_slash = new_validators.into_iter().take(to_slash_count).collect::<Vec<_>>();
1106 let one_slashed = to_slash.first().cloned().unwrap();
1107 let offences = to_slash
1108 .into_iter()
1109 .map(|offender| rc_client::Offence {
1110 offender,
1111 reporters: Default::default(),
1112 slash_fraction: Perbill::from_percent(50),
1113 })
1114 .collect::<Vec<_>>();
1115 let slash_session = 42;
1116
1117 ensure!(
1119 !ValidatorSlashInEra::<T>::contains_key(initial_era + 1, &one_slashed),
1120 "offence submitted???"
1121 );
1122
1123 #[block]
1124 {
1125 <crate::Pallet<T> as rc_client::AHStakingInterface>::on_new_offences(
1126 slash_session,
1127 offences,
1128 );
1129 }
1130
1131 ensure!(
1133 ValidatorSlashInEra::<T>::contains_key(initial_era + 1, one_slashed),
1134 "offence not submitted"
1135 );
1136
1137 Ok(())
1138 }
1139
1140 #[benchmark]
1141 fn rc_on_session_report() -> Result<(), BenchmarkError> {
1142 let initial_planned_era = Rotator::<T>::planned_era();
1143 let initial_active_era = Rotator::<T>::active_era();
1144
1145 crate::testing_utils::create_validators_with_nominators_for_era::<T>(
1148 10, 50, 2, false, None,
1149 )?;
1150
1151 let _new_validators = Rotator::<T>::legacy_insta_plan_era();
1153 ensure!(
1154 CurrentEra::<T>::get().unwrap() == initial_planned_era + 1,
1155 "era should be incremented"
1156 );
1157
1158 let validator_points = (0..T::MaxValidatorSet::get())
1160 .map(|v| (account::<T::AccountId>("random", v, SEED), v))
1161 .collect::<Vec<_>>();
1162 let activation_timestamp = Some((1u64, initial_planned_era + 1));
1163 let report = rc_client::SessionReport {
1164 end_index: 42,
1165 leftover: false,
1166 validator_points,
1167 activation_timestamp,
1168 };
1169
1170 #[block]
1171 {
1172 <crate::Pallet<T> as rc_client::AHStakingInterface>::on_relay_session_report(report);
1173 }
1174
1175 ensure!(Rotator::<T>::active_era() == initial_active_era + 1, "active era not bumped");
1176 Ok(())
1177 }
1178
1179 fn setup_era_for_pruning<T: Config>(v: u32) -> EraIndex {
1181 let validators = v;
1182 let era = 7;
1183
1184 let history_depth = T::HistoryDepth::get();
1187 let active_era = era + history_depth + 1;
1188 crate::ActiveEra::<T>::put(crate::ActiveEraInfo { index: active_era, start: Some(0) });
1189
1190 let max_total_nominators_per_validator =
1194 <T::ElectionProvider as ElectionProvider>::MaxBackersPerWinnerFinal::get();
1195 let exposed_nominators_per_validator = max_total_nominators_per_validator / validators;
1196
1197 for i in 0..validators {
1199 let validator = account::<T::AccountId>("validator", i, SEED);
1200 ErasValidatorPrefs::<T>::insert(era, validator.clone(), ValidatorPrefs::default())
1201 }
1202
1203 let pages: WeakBoundedVec<_, _> = (0..crate::ClaimedRewardsBound::<T>::get())
1205 .collect::<Vec<_>>()
1206 .try_into()
1207 .unwrap();
1208 for i in 0..validators {
1209 let validator = account::<T::AccountId>("validator", i, SEED);
1210 ClaimedRewards::<T>::insert(era, validator.clone(), pages.clone())
1211 }
1212
1213 (0..validators)
1215 .map(|validator_index| account::<T::AccountId>("validator", validator_index, SEED))
1216 .for_each(|validator| {
1217 let exposure = sp_staking::Exposure::<T::AccountId, BalanceOf<T>> {
1218 own: T::Currency::minimum_balance(),
1219 total: T::Currency::minimum_balance() *
1220 (exposed_nominators_per_validator + 1).into(),
1221 others: (0..exposed_nominators_per_validator)
1222 .map(|n| {
1223 let nominator = account::<T::AccountId>("nominator", n, SEED);
1224 IndividualExposure {
1225 who: nominator,
1226 value: T::Currency::minimum_balance(),
1227 }
1228 })
1229 .collect::<Vec<_>>(),
1230 };
1231 Eras::<T>::upsert_exposure(era, &validator, exposure);
1232 });
1233
1234 ErasValidatorReward::<T>::insert(era, BalanceOf::<T>::max_value());
1236
1237 let reward_points = crate::EraRewardPoints::<T> {
1239 total: 77777,
1240 individual: (0..validators)
1241 .map(|v| account::<T::AccountId>("validator", v, SEED))
1242 .map(|v| (v, 7))
1243 .collect::<BTreeMap<_, _>>()
1244 .try_into()
1245 .unwrap(),
1246 };
1247 ErasRewardPoints::<T>::insert(era, reward_points);
1248
1249 ErasTotalStake::<T>::insert(era, BalanceOf::<T>::max_value());
1251
1252 era
1253 }
1254
1255 fn validate_pruning_weight<T: Config>(
1257 result: &frame_support::dispatch::DispatchResultWithPostInfo,
1258 step_name: &str,
1259 validator_count: u32,
1260 ) {
1261 assert!(
1262 result.is_ok(),
1263 "Benchmark {} should succeed with v={}",
1264 step_name,
1265 validator_count
1266 );
1267
1268 let post_info = result.unwrap();
1269 let actual_ref_time = post_info
1270 .actual_weight
1271 .expect(&format!(
1272 "Should report actual weight for {} with v={}",
1273 step_name, validator_count
1274 ))
1275 .ref_time();
1276
1277 assert!(
1278 actual_ref_time > 0,
1279 "Should report non-zero ref_time for {} with v={}",
1280 step_name,
1281 validator_count
1282 );
1283 }
1285
1286 #[benchmark(pov_mode = Measured)]
1288 fn prune_era_stakers_paged(
1289 v: Linear<1, { T::MaxValidatorSet::get() }>,
1290 ) -> Result<(), BenchmarkError> {
1291 let era = setup_era_for_pruning::<T>(v);
1292 EraPruningState::<T>::insert(era, PruningStep::ErasStakersPaged);
1293
1294 let caller: T::AccountId = whitelisted_caller();
1295
1296 let result;
1297 #[block]
1298 {
1299 result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1300 }
1301
1302 validate_pruning_weight::<T>(&result, "ErasStakersPaged", v);
1303
1304 Ok(())
1305 }
1306
1307 #[benchmark(pov_mode = Measured)]
1309 fn prune_era_stakers_overview(
1310 v: Linear<1, { T::MaxValidatorSet::get() }>,
1311 ) -> Result<(), BenchmarkError> {
1312 let era = setup_era_for_pruning::<T>(v);
1313 EraPruningState::<T>::insert(era, PruningStep::ErasStakersOverview);
1314
1315 let caller: T::AccountId = whitelisted_caller();
1316
1317 let result;
1318 #[block]
1319 {
1320 result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1321 }
1322
1323 validate_pruning_weight::<T>(&result, "ErasStakersOverview", v);
1324
1325 Ok(())
1326 }
1327
1328 #[benchmark(pov_mode = Measured)]
1330 fn prune_era_validator_prefs(
1331 v: Linear<1, { T::MaxValidatorSet::get() }>,
1332 ) -> Result<(), BenchmarkError> {
1333 let era = setup_era_for_pruning::<T>(v);
1334 EraPruningState::<T>::insert(era, PruningStep::ErasValidatorPrefs);
1335
1336 let caller: T::AccountId = whitelisted_caller();
1337
1338 let result;
1339 #[block]
1340 {
1341 result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1342 }
1343
1344 validate_pruning_weight::<T>(&result, "ErasValidatorPrefs", v);
1345
1346 Ok(())
1347 }
1348
1349 #[benchmark(pov_mode = Measured)]
1351 fn prune_era_claimed_rewards(
1352 v: Linear<1, { T::MaxValidatorSet::get() }>,
1353 ) -> Result<(), BenchmarkError> {
1354 let era = setup_era_for_pruning::<T>(v);
1355 EraPruningState::<T>::insert(era, PruningStep::ClaimedRewards);
1356
1357 let caller: T::AccountId = whitelisted_caller();
1358
1359 let result;
1360 #[block]
1361 {
1362 result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1363 }
1364
1365 validate_pruning_weight::<T>(&result, "ClaimedRewards", v);
1366
1367 Ok(())
1368 }
1369
1370 #[benchmark(pov_mode = Measured)]
1372 fn prune_era_validator_reward() -> Result<(), BenchmarkError> {
1373 let era = setup_era_for_pruning::<T>(1);
1374 EraPruningState::<T>::insert(era, PruningStep::ErasValidatorReward);
1375
1376 let caller: T::AccountId = whitelisted_caller();
1377
1378 let result;
1379 #[block]
1380 {
1381 result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1382 }
1383
1384 validate_pruning_weight::<T>(&result, "ErasValidatorReward", 1);
1385
1386 Ok(())
1387 }
1388
1389 #[benchmark(pov_mode = Measured)]
1391 fn prune_era_reward_points() -> Result<(), BenchmarkError> {
1392 let era = setup_era_for_pruning::<T>(1);
1393 EraPruningState::<T>::insert(era, PruningStep::ErasRewardPoints);
1394
1395 let caller: T::AccountId = whitelisted_caller();
1396
1397 let result;
1398 #[block]
1399 {
1400 result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1401 }
1402
1403 validate_pruning_weight::<T>(&result, "ErasRewardPoints", 1);
1404
1405 Ok(())
1406 }
1407
1408 #[benchmark(pov_mode = Measured)]
1410 fn prune_era_total_stake() -> Result<(), BenchmarkError> {
1411 let era = setup_era_for_pruning::<T>(1);
1412 EraPruningState::<T>::insert(era, PruningStep::ErasTotalStake);
1413
1414 let caller: T::AccountId = whitelisted_caller();
1415
1416 let result;
1417 #[block]
1418 {
1419 result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1420 }
1421
1422 validate_pruning_weight::<T>(&result, "ErasTotalStake", 1);
1423
1424 Ok(())
1425 }
1426
1427 impl_benchmark_test_suite!(
1428 Staking,
1429 crate::mock::ExtBuilder::default().has_stakers(true),
1430 crate::mock::Test,
1431 exec_name = build_and_execute
1432 );
1433}
1434
1435#[cfg(test)]
1436mod tests {
1437 use super::*;
1438 use crate::mock::{ExtBuilder, RuntimeOrigin, Staking, Test};
1439 use frame_support::assert_ok;
1440
1441 #[test]
1442 fn create_validators_with_nominators_for_era_works() {
1443 ExtBuilder::default().build_and_execute(|| {
1444 let v = 10;
1445 let n = 100;
1446
1447 create_validators_with_nominators_for_era::<Test>(
1448 v,
1449 n,
1450 MaxNominationsOf::<Test>::get() as usize,
1451 false,
1452 None,
1453 )
1454 .unwrap();
1455
1456 let count_validators = Validators::<Test>::iter().count();
1457 let count_nominators = Nominators::<Test>::iter().count();
1458
1459 assert_eq!(count_validators, Validators::<Test>::count() as usize);
1460 assert_eq!(count_nominators, Nominators::<Test>::count() as usize);
1461
1462 assert_eq!(count_validators, v as usize);
1463 assert_eq!(count_nominators, n as usize);
1464 });
1465 }
1466
1467 #[test]
1468 fn create_validator_with_nominators_works() {
1469 ExtBuilder::default().build_and_execute(|| {
1470 let n = 10;
1471
1472 let (validator_stash, nominators, current_era) =
1473 create_validator_with_nominators::<Test>(
1474 n,
1475 <<Test as Config>::MaxExposurePageSize as Get<_>>::get(),
1476 false,
1477 false,
1478 RewardDestination::Staked,
1479 )
1480 .unwrap();
1481
1482 assert_eq!(nominators.len() as u32, n);
1483
1484 let original_stakeable_balance = asset::stakeable_balance::<Test>(&validator_stash);
1485 assert_ok!(Staking::payout_stakers_by_page(
1486 RuntimeOrigin::signed(1337),
1487 validator_stash,
1488 current_era,
1489 0
1490 ));
1491 let new_stakeable_balance = asset::stakeable_balance::<Test>(&validator_stash);
1492
1493 assert!(original_stakeable_balance < new_stakeable_balance);
1495 });
1496 }
1497}