referrerpolicy=no-referrer-when-downgrade

pallet_staking_async/
benchmarking.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//! Staking pallet benchmarking.
19
20use 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, pallet_prelude::*, storage::bounded_vec::BoundedVec, traits::TryCollect,
33};
34use frame_system::RawOrigin;
35use pallet_staking_async_rc_client as rc_client;
36use sp_runtime::{
37	traits::{Bounded, One, StaticLookup, Zero},
38	Perbill, Percent, Saturating,
39};
40use sp_staking::currency_to_vote::CurrencyToVote;
41use testing_utils::*;
42
43const SEED: u32 = 0;
44
45// This function clears all existing validators and nominators from the set, and generates one new
46// validator being nominated by n nominators, and returns the validator stash account and the
47// nominators' stash and controller. It also starts plans a new era with this new stakers, and
48// returns the planned era index.
49pub(crate) fn create_validator_with_nominators<T: Config>(
50	n: u32,
51	upper_bound: u32,
52	dead_controller: bool,
53	unique_controller: bool,
54	destination: RewardDestination<T::AccountId>,
55) -> Result<(T::AccountId, Vec<(T::AccountId, T::AccountId)>, EraIndex), &'static str> {
56	// TODO: this can be replaced with `testing_utils` version?
57	// Clean up any existing state.
58	clear_validators_and_nominators::<T>();
59	let mut points_total = 0;
60	let mut points_individual = Vec::new();
61
62	let (v_stash, v_controller) = if unique_controller {
63		create_unique_stash_controller::<T>(0, 100, destination.clone(), false)?
64	} else {
65		create_stash_controller::<T>(0, 100, destination.clone())?
66	};
67
68	let validator_prefs =
69		ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
70	Staking::<T>::validate(RawOrigin::Signed(v_controller).into(), validator_prefs)?;
71	let stash_lookup = T::Lookup::unlookup(v_stash.clone());
72
73	points_total += 10;
74	points_individual.push((v_stash.clone(), 10));
75
76	let original_nominator_count = Nominators::<T>::count();
77	let mut nominators = Vec::new();
78
79	// Give the validator n nominators, but keep total users in the system the same.
80	for i in 0..upper_bound {
81		let (n_stash, n_controller) = if !dead_controller {
82			create_stash_controller::<T>(u32::MAX - i, 100, destination.clone())?
83		} else {
84			create_unique_stash_controller::<T>(u32::MAX - i, 100, destination.clone(), true)?
85		};
86		if i < n {
87			Staking::<T>::nominate(
88				RawOrigin::Signed(n_controller.clone()).into(),
89				vec![stash_lookup.clone()],
90			)?;
91			nominators.push((n_stash, n_controller));
92		}
93	}
94
95	ValidatorCount::<T>::put(1);
96
97	// Start a new Era
98	let new_validators = Rotator::<T>::legacy_insta_plan_era();
99	let planned_era = CurrentEra::<T>::get().unwrap_or_default();
100
101	assert_eq!(new_validators.len(), 1, "New validators is not 1");
102	assert_eq!(new_validators[0], v_stash, "Our validator was not selected");
103	assert_ne!(Validators::<T>::count(), 0, "New validators count wrong");
104	assert_eq!(
105		Nominators::<T>::count(),
106		original_nominator_count + nominators.len() as u32,
107		"New nominators count wrong"
108	);
109
110	// Give Era Points
111	let reward = EraRewardPoints::<T> {
112		total: points_total,
113		individual: points_individual.into_iter().try_collect()?,
114	};
115
116	ErasRewardPoints::<T>::insert(planned_era, reward);
117
118	// Create reward pool
119	let total_payout = asset::existential_deposit::<T>()
120		.saturating_mul(upper_bound.into())
121		.saturating_mul(1000u32.into());
122	<ErasValidatorReward<T>>::insert(planned_era, total_payout);
123
124	Ok((v_stash, nominators, planned_era))
125}
126
127struct ListScenario<T: Config> {
128	/// Stash that is expected to be moved.
129	origin_stash1: T::AccountId,
130	/// Controller of the Stash that is expected to be moved.
131	origin_controller1: T::AccountId,
132	dest_weight: BalanceOf<T>,
133}
134
135impl<T: Config> ListScenario<T> {
136	/// An expensive scenario for bags-list implementation:
137	///
138	/// - the node to be updated (r) is the head of a bag that has at least one other node. The bag
139	///   itself will need to be read and written to update its head. The node pointed to by r.next
140	///   will need to be read and written as it will need to have its prev pointer updated. Note
141	///   that there are two other worst case scenarios for bag removal: 1) the node is a tail and
142	///   2) the node is a middle node with prev and next; all scenarios end up with the same number
143	///   of storage reads and writes.
144	///
145	/// - the destination bag has at least one node, which will need its next pointer updated.
146	///
147	/// NOTE: while this scenario specifically targets a worst case for the bags-list, it should
148	/// also elicit a worst case for other known `VoterList` implementations; although
149	/// this may not be true against unknown `VoterList` implementations.
150	fn new(origin_weight: BalanceOf<T>, is_increase: bool) -> Result<Self, &'static str> {
151		ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0");
152
153		// burn the entire issuance.
154		let i = asset::burn::<T>(asset::total_issuance::<T>());
155		core::mem::forget(i);
156
157		// make sure `account("random_validator", 0, SEED)` is a validator
158		let validator_account = account("random_validator", 0, SEED);
159		let validator_stake = asset::existential_deposit::<T>() * 1000u32.into();
160		asset::set_stakeable_balance::<T>(&validator_account, validator_stake);
161		assert_ok!(Staking::<T>::bond(
162			RawOrigin::Signed(validator_account.clone()).into(),
163			validator_stake / 2u32.into(),
164			RewardDestination::Staked
165		));
166		assert_ok!(Staking::<T>::validate(
167			RawOrigin::Signed(validator_account.clone()).into(),
168			Default::default()
169		));
170
171		// create accounts with the origin weight
172		let (origin_stash1, origin_controller1) = create_stash_controller_with_balance::<T>(
173			USER_SEED + 2,
174			origin_weight,
175			RewardDestination::Staked,
176		)?;
177		Staking::<T>::nominate(
178			RawOrigin::Signed(origin_controller1.clone()).into(),
179			// NOTE: these don't really need to be validators.
180			vec![T::Lookup::unlookup(validator_account.clone())],
181		)?;
182
183		let (_origin_stash2, origin_controller2) = create_stash_controller_with_balance::<T>(
184			USER_SEED + 3,
185			origin_weight,
186			RewardDestination::Staked,
187		)?;
188		Staking::<T>::nominate(
189			RawOrigin::Signed(origin_controller2).into(),
190			vec![T::Lookup::unlookup(validator_account.clone())],
191		)?;
192
193		// find a destination weight that will trigger the worst case scenario
194		let dest_weight_as_vote =
195			T::VoterList::score_update_worst_case(&origin_stash1, is_increase);
196
197		let total_issuance = asset::total_issuance::<T>();
198
199		let dest_weight =
200			T::CurrencyToVote::to_currency(dest_weight_as_vote as u128, total_issuance);
201
202		// create an account with the worst case destination weight
203		let (_dest_stash1, dest_controller1) = create_stash_controller_with_balance::<T>(
204			USER_SEED + 1,
205			dest_weight,
206			RewardDestination::Staked,
207		)?;
208		Staking::<T>::nominate(
209			RawOrigin::Signed(dest_controller1).into(),
210			vec![T::Lookup::unlookup(validator_account)],
211		)?;
212
213		Ok(ListScenario { origin_stash1, origin_controller1, dest_weight })
214	}
215}
216
217const USER_SEED: u32 = 999666;
218
219#[benchmarks]
220mod benchmarks {
221	use super::*;
222	use alloc::format;
223
224	#[benchmark]
225	fn bond() {
226		let stash = create_funded_user::<T>("stash", USER_SEED, 100);
227		let reward_destination = RewardDestination::Staked;
228		let amount = asset::existential_deposit::<T>() * 10u32.into();
229		whitelist_account!(stash);
230
231		#[extrinsic_call]
232		_(RawOrigin::Signed(stash.clone()), amount, reward_destination);
233
234		assert!(Bonded::<T>::contains_key(stash.clone()));
235		assert!(Ledger::<T>::contains_key(stash));
236	}
237
238	#[benchmark]
239	fn bond_extra() -> Result<(), BenchmarkError> {
240		// clean up any existing state.
241		clear_validators_and_nominators::<T>();
242
243		let origin_weight = Staking::<T>::min_nominator_bond();
244
245		// setup the worst case list scenario.
246
247		// the weight the nominator will start at.
248		let scenario = ListScenario::<T>::new(origin_weight, true)?;
249
250		let max_additional = scenario.dest_weight - origin_weight;
251
252		let stash = scenario.origin_stash1.clone();
253		let controller = scenario.origin_controller1;
254		let original_bonded: BalanceOf<T> = Ledger::<T>::get(&controller)
255			.map(|l| l.active)
256			.ok_or("ledger not created after")?;
257
258		let _ = asset::mint_into_existing::<T>(
259			&stash,
260			max_additional + asset::existential_deposit::<T>(),
261		)
262		.unwrap();
263
264		whitelist_account!(stash);
265
266		#[extrinsic_call]
267		_(RawOrigin::Signed(stash), max_additional);
268
269		let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created after")?;
270		let new_bonded: BalanceOf<T> = ledger.active;
271		assert!(original_bonded < new_bonded);
272
273		Ok(())
274	}
275
276	#[benchmark]
277	fn unbond() -> Result<(), BenchmarkError> {
278		// clean up any existing state.
279		clear_validators_and_nominators::<T>();
280
281		// the weight the nominator will start at. The value used here is expected to be
282		// significantly higher than the first position in a list (e.g. the first bag threshold).
283		let origin_weight = BalanceOf::<T>::try_from(952_994_955_240_703u128)
284			.map_err(|_| "balance expected to be a u128")
285			.unwrap();
286		let scenario = ListScenario::<T>::new(origin_weight, false)?;
287
288		let controller = scenario.origin_controller1.clone();
289		let amount = origin_weight - scenario.dest_weight;
290		let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created before")?;
291		let original_bonded: BalanceOf<T> = ledger.active;
292
293		whitelist_account!(controller);
294
295		#[extrinsic_call]
296		_(RawOrigin::Signed(controller.clone()), amount);
297
298		let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created after")?;
299		let new_bonded: BalanceOf<T> = ledger.active;
300		assert!(original_bonded > new_bonded);
301
302		Ok(())
303	}
304
305	#[benchmark]
306	// Withdraw only updates the ledger
307	fn withdraw_unbonded_update() -> Result<(), BenchmarkError> {
308		let (_, controller) = create_stash_controller::<T>(0, 100, RewardDestination::Staked)?;
309		let amount = asset::existential_deposit::<T>() * 5u32.into(); // Half of total
310		Staking::<T>::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?;
311		set_active_era::<T>(EraIndex::max_value());
312		let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created before")?;
313		let original_total: BalanceOf<T> = ledger.total;
314		whitelist_account!(controller);
315
316		#[extrinsic_call]
317		withdraw_unbonded(RawOrigin::Signed(controller.clone()), 0);
318
319		let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created after")?;
320		let new_total: BalanceOf<T> = ledger.total;
321		assert!(original_total > new_total);
322
323		Ok(())
324	}
325
326	#[benchmark]
327	// Worst case scenario, everything is removed after the bonding duration
328	fn withdraw_unbonded_kill() -> Result<(), BenchmarkError> {
329		// clean up any existing state.
330		clear_validators_and_nominators::<T>();
331
332		let origin_weight = Staking::<T>::min_nominator_bond();
333
334		// setup a worst case list scenario. Note that we don't care about the setup of the
335		// destination position because we are doing a removal from the list but no insert.
336		let scenario = ListScenario::<T>::new(origin_weight, true)?;
337		let controller = scenario.origin_controller1.clone();
338		let stash = scenario.origin_stash1;
339		assert!(T::VoterList::contains(&stash));
340
341		let ed = asset::existential_deposit::<T>();
342		let mut ledger = Ledger::<T>::get(&controller).unwrap();
343		ledger.active = ed - One::one();
344		Ledger::<T>::insert(&controller, ledger);
345		set_active_era::<T>(EraIndex::max_value());
346
347		whitelist_account!(controller);
348
349		#[extrinsic_call]
350		withdraw_unbonded(RawOrigin::Signed(controller.clone()), 0);
351
352		assert!(!Ledger::<T>::contains_key(controller));
353		assert!(!T::VoterList::contains(&stash));
354
355		Ok(())
356	}
357
358	#[benchmark]
359	fn validate() -> Result<(), BenchmarkError> {
360		let (stash, controller) = create_stash_controller::<T>(
361			MaxNominationsOf::<T>::get() - 1,
362			100,
363			RewardDestination::Staked,
364		)?;
365		// because it is chilled.
366		assert!(!T::VoterList::contains(&stash));
367
368		let prefs = ValidatorPrefs::default();
369		whitelist_account!(controller);
370
371		#[extrinsic_call]
372		_(RawOrigin::Signed(controller), prefs);
373
374		assert!(Validators::<T>::contains_key(&stash));
375		assert!(T::VoterList::contains(&stash));
376
377		Ok(())
378	}
379
380	#[benchmark]
381	fn kick(
382		// scenario: we want to kick `k` nominators from nominating us (we are a validator).
383		// we'll assume that `k` is under 128 for the purposes of determining the slope.
384		// each nominator should have `T::MaxNominations::get()` validators nominated, and our
385		// validator should be somewhere in there.
386		k: Linear<1, 128>,
387	) -> Result<(), BenchmarkError> {
388		// these are the other validators; there are `T::MaxNominations::get() - 1` of them, so
389		// there are a total of `T::MaxNominations::get()` validators in the system.
390		let rest_of_validators =
391			create_validators_with_seed::<T>(MaxNominationsOf::<T>::get() - 1, 100, 415)?;
392
393		// this is the validator that will be kicking.
394		let (stash, controller) = create_stash_controller::<T>(
395			MaxNominationsOf::<T>::get() - 1,
396			100,
397			RewardDestination::Staked,
398		)?;
399		let stash_lookup = T::Lookup::unlookup(stash.clone());
400
401		// they start validating.
402		Staking::<T>::validate(RawOrigin::Signed(controller.clone()).into(), Default::default())?;
403
404		// we now create the nominators. there will be `k` of them; each will nominate all
405		// validators. we will then kick each of the `k` nominators from the main validator.
406		let mut nominator_stashes = Vec::with_capacity(k as usize);
407		for i in 0..k {
408			// create a nominator stash.
409			let (n_stash, n_controller) = create_stash_controller::<T>(
410				MaxNominationsOf::<T>::get() + i,
411				100,
412				RewardDestination::Staked,
413			)?;
414
415			// bake the nominations; we first clone them from the rest of the validators.
416			let mut nominations = rest_of_validators.clone();
417			// then insert "our" validator somewhere in there (we vary it) to avoid accidental
418			// optimisations/pessimisations.
419			nominations.insert(i as usize % (nominations.len() + 1), stash_lookup.clone());
420			// then we nominate.
421			Staking::<T>::nominate(RawOrigin::Signed(n_controller.clone()).into(), nominations)?;
422
423			nominator_stashes.push(n_stash);
424		}
425
426		// all nominators now should be nominating our validator...
427		for n in nominator_stashes.iter() {
428			assert!(Nominators::<T>::get(n).unwrap().targets.contains(&stash));
429		}
430
431		// we need the unlookuped version of the nominator stash for the kick.
432		let kicks = nominator_stashes
433			.iter()
434			.map(|n| T::Lookup::unlookup(n.clone()))
435			.collect::<Vec<_>>();
436
437		whitelist_account!(controller);
438
439		#[extrinsic_call]
440		_(RawOrigin::Signed(controller), kicks);
441
442		// all nominators now should *not* be nominating our validator...
443		for n in nominator_stashes.iter() {
444			assert!(!Nominators::<T>::get(n).unwrap().targets.contains(&stash));
445		}
446
447		Ok(())
448	}
449
450	#[benchmark]
451	// Worst case scenario, T::MaxNominations::get()
452	fn nominate(n: Linear<1, { MaxNominationsOf::<T>::get() }>) -> Result<(), BenchmarkError> {
453		// clean up any existing state.
454		clear_validators_and_nominators::<T>();
455
456		let origin_weight = Staking::<T>::min_nominator_bond();
457
458		// setup a worst case list scenario. Note we don't care about the destination position,
459		// because we are just doing an insert into the origin position.
460		ListScenario::<T>::new(origin_weight, true)?;
461		let (stash, controller) = create_stash_controller_with_balance::<T>(
462			SEED + MaxNominationsOf::<T>::get() + 1, /* make sure the account does not conflict
463			                                          * with others */
464			origin_weight,
465			RewardDestination::Staked,
466		)
467		.unwrap();
468
469		assert!(!Nominators::<T>::contains_key(&stash));
470		assert!(!T::VoterList::contains(&stash));
471
472		let validators = create_validators::<T>(n, 100).unwrap();
473		whitelist_account!(controller);
474
475		#[extrinsic_call]
476		_(RawOrigin::Signed(controller), validators);
477
478		assert!(Nominators::<T>::contains_key(&stash));
479		assert!(T::VoterList::contains(&stash));
480
481		Ok(())
482	}
483
484	#[benchmark]
485	fn chill() -> Result<(), BenchmarkError> {
486		// clean up any existing state.
487		clear_validators_and_nominators::<T>();
488
489		let origin_weight = Staking::<T>::min_nominator_bond();
490
491		// setup a worst case list scenario. Note that we don't care about the setup of the
492		// destination position because we are doing a removal from the list but no insert.
493		let scenario = ListScenario::<T>::new(origin_weight, true)?;
494		let controller = scenario.origin_controller1.clone();
495		let stash = scenario.origin_stash1;
496		assert!(T::VoterList::contains(&stash));
497
498		whitelist_account!(controller);
499
500		#[extrinsic_call]
501		_(RawOrigin::Signed(controller));
502
503		assert!(!T::VoterList::contains(&stash));
504
505		Ok(())
506	}
507
508	#[benchmark]
509	fn set_payee() -> Result<(), BenchmarkError> {
510		let (stash, controller) =
511			create_stash_controller::<T>(USER_SEED, 100, RewardDestination::Staked)?;
512		assert_eq!(Payee::<T>::get(&stash), Some(RewardDestination::Staked));
513		whitelist_account!(controller);
514
515		#[extrinsic_call]
516		_(RawOrigin::Signed(controller.clone()), RewardDestination::Account(controller.clone()));
517
518		assert_eq!(Payee::<T>::get(&stash), Some(RewardDestination::Account(controller)));
519
520		Ok(())
521	}
522
523	#[benchmark]
524	fn update_payee() -> Result<(), BenchmarkError> {
525		let (stash, controller) =
526			create_stash_controller::<T>(USER_SEED, 100, RewardDestination::Staked)?;
527		Payee::<T>::insert(&stash, {
528			#[allow(deprecated)]
529			RewardDestination::Controller
530		});
531		whitelist_account!(controller);
532
533		#[extrinsic_call]
534		_(RawOrigin::Signed(controller.clone()), controller.clone());
535
536		assert_eq!(Payee::<T>::get(&stash), Some(RewardDestination::Account(controller)));
537
538		Ok(())
539	}
540
541	#[benchmark]
542	fn set_controller() -> Result<(), BenchmarkError> {
543		let (stash, ctlr) =
544			create_unique_stash_controller::<T>(9000, 100, RewardDestination::Staked, false)?;
545		// ensure `ctlr` is the currently stored controller.
546		assert!(!Ledger::<T>::contains_key(&stash));
547		assert!(Ledger::<T>::contains_key(&ctlr));
548		assert_eq!(Bonded::<T>::get(&stash), Some(ctlr.clone()));
549
550		whitelist_account!(stash);
551
552		#[extrinsic_call]
553		_(RawOrigin::Signed(stash.clone()));
554
555		assert!(Ledger::<T>::contains_key(&stash));
556
557		Ok(())
558	}
559
560	#[benchmark]
561	fn set_validator_count() {
562		let validator_count = T::MaxValidatorSet::get() - 1;
563
564		#[extrinsic_call]
565		_(RawOrigin::Root, validator_count);
566
567		assert_eq!(ValidatorCount::<T>::get(), validator_count);
568	}
569
570	#[benchmark]
571	fn force_no_eras() {
572		#[extrinsic_call]
573		_(RawOrigin::Root);
574
575		assert_eq!(ForceEra::<T>::get(), Forcing::ForceNone);
576	}
577
578	#[benchmark]
579	fn force_new_era() {
580		#[extrinsic_call]
581		_(RawOrigin::Root);
582
583		assert_eq!(ForceEra::<T>::get(), Forcing::ForceNew);
584	}
585
586	#[benchmark]
587	fn force_new_era_always() {
588		#[extrinsic_call]
589		_(RawOrigin::Root);
590
591		assert_eq!(ForceEra::<T>::get(), Forcing::ForceAlways);
592	}
593
594	#[benchmark]
595	// Worst case scenario, the list of invulnerables is very long.
596	fn set_invulnerables(v: Linear<0, { T::MaxInvulnerables::get() }>) {
597		let mut invulnerables = Vec::new();
598		for i in 0..v {
599			invulnerables.push(account("invulnerable", i, SEED));
600		}
601
602		#[extrinsic_call]
603		_(RawOrigin::Root, invulnerables);
604
605		assert_eq!(Invulnerables::<T>::get().len(), v as usize);
606	}
607
608	#[benchmark]
609	fn deprecate_controller_batch(
610		// We pass a dynamic number of controllers to the benchmark, up to
611		// `MaxControllersInDeprecationBatch`.
612		u: Linear<0, { T::MaxControllersInDeprecationBatch::get() }>,
613	) -> Result<(), BenchmarkError> {
614		let mut controllers: Vec<_> = vec![];
615		let mut stashes: Vec<_> = vec![];
616		for i in 0..u as u32 {
617			let (stash, controller) =
618				create_unique_stash_controller::<T>(i, 100, RewardDestination::Staked, false)?;
619			controllers.push(controller);
620			stashes.push(stash);
621		}
622		let bounded_controllers: BoundedVec<_, T::MaxControllersInDeprecationBatch> =
623			BoundedVec::try_from(controllers.clone()).unwrap();
624
625		#[extrinsic_call]
626		_(RawOrigin::Root, bounded_controllers);
627
628		for i in 0..u as u32 {
629			let stash = &stashes[i as usize];
630			let controller = &controllers[i as usize];
631			// Ledger no longer keyed by controller.
632			assert_eq!(Ledger::<T>::get(controller), None);
633			// Bonded now maps to the stash.
634			assert_eq!(Bonded::<T>::get(stash), Some(stash.clone()));
635			// Ledger is now keyed by stash.
636			assert_eq!(Ledger::<T>::get(stash).unwrap().stash, *stash);
637		}
638
639		Ok(())
640	}
641
642	#[benchmark]
643	fn force_unstake() -> Result<(), BenchmarkError> {
644		// Clean up any existing state.
645		clear_validators_and_nominators::<T>();
646
647		let origin_weight = Staking::<T>::min_nominator_bond();
648
649		// setup a worst case list scenario. Note that we don't care about the setup of the
650		// destination position because we are doing a removal from the list but no insert.
651		let scenario = ListScenario::<T>::new(origin_weight, true)?;
652		let controller = scenario.origin_controller1.clone();
653		let stash = scenario.origin_stash1;
654		assert!(T::VoterList::contains(&stash));
655
656		#[extrinsic_call]
657		_(RawOrigin::Root, stash.clone(), 0);
658
659		assert!(!Ledger::<T>::contains_key(&controller));
660		assert!(!T::VoterList::contains(&stash));
661
662		Ok(())
663	}
664
665	#[benchmark]
666	fn cancel_deferred_slash(s: Linear<1, { T::MaxValidatorSet::get() }>) {
667		let era = EraIndex::one();
668
669		// Create validators and insert slashes
670		let validators: Vec<_> = (0..s)
671			.map(|i| {
672				let validator: T::AccountId = account("validator", i, SEED);
673
674				// Insert slash for this validator
675				let slash_key = (validator.clone(), Perbill::from_percent(10), 0);
676				let unapplied_slash = UnappliedSlash::<T> {
677					validator: validator.clone(),
678					own: Zero::zero(),
679					others: WeakBoundedVec::default(),
680					reporter: Default::default(),
681					payout: Zero::zero(),
682				};
683				UnappliedSlashes::<T>::insert(era, slash_key, unapplied_slash);
684
685				validator
686			})
687			.collect();
688
689		// Convert validators to tuples with 10% slash fraction (matching the slashes created above)
690		let validator_slashes: Vec<_> =
691			validators.into_iter().map(|v| (v, Perbill::from_percent(10))).collect();
692
693		#[extrinsic_call]
694		_(RawOrigin::Root, era, validator_slashes.clone());
695
696		// Ensure cancelled slashes are stored correctly
697		let cancelled_slashes = CancelledSlashes::<T>::get(era);
698		assert_eq!(cancelled_slashes.len(), s as usize);
699	}
700
701	#[benchmark]
702	fn payout_stakers_alive_staked(
703		n: Linear<0, { T::MaxExposurePageSize::get() as u32 }>,
704	) -> Result<(), BenchmarkError> {
705		let (validator, nominators, current_era) = create_validator_with_nominators::<T>(
706			n,
707			T::MaxExposurePageSize::get() as u32,
708			false,
709			true,
710			RewardDestination::Staked,
711		)?;
712
713		// set the commission for this particular era as well.
714		<ErasValidatorPrefs<T>>::insert(
715			current_era,
716			validator.clone(),
717			Validators::<T>::get(&validator),
718		);
719
720		let caller = whitelisted_caller();
721		let balance_before = asset::stakeable_balance::<T>(&validator);
722		let mut nominator_balances_before = Vec::new();
723		for (stash, _) in &nominators {
724			let balance = asset::stakeable_balance::<T>(stash);
725			nominator_balances_before.push(balance);
726		}
727
728		#[extrinsic_call]
729		payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era);
730
731		let balance_after = asset::stakeable_balance::<T>(&validator);
732		ensure!(
733			balance_before < balance_after,
734			"Balance of validator stash should have increased after payout.",
735		);
736		for ((stash, _), balance_before) in nominators.iter().zip(nominator_balances_before.iter())
737		{
738			let balance_after = asset::stakeable_balance::<T>(stash);
739			ensure!(
740				balance_before < &balance_after,
741				"Balance of nominator stash should have increased after payout.",
742			);
743		}
744
745		Ok(())
746	}
747
748	#[benchmark]
749	fn rebond(l: Linear<1, { T::MaxUnlockingChunks::get() as u32 }>) -> Result<(), BenchmarkError> {
750		// clean up any existing state.
751		clear_validators_and_nominators::<T>();
752
753		let origin_weight = Pallet::<T>::min_nominator_bond()
754			// we use 100 to play friendly with the list threshold values in the mock
755			.max(100u32.into());
756
757		// setup a worst case list scenario.
758		let scenario = ListScenario::<T>::new(origin_weight, true)?;
759		let dest_weight = scenario.dest_weight;
760
761		// rebond an amount that will give the user dest_weight
762		let rebond_amount = dest_weight - origin_weight;
763
764		// spread that amount to rebond across `l` unlocking chunks,
765		let value = rebond_amount / l.into();
766		// if `value` is zero, we need a greater delta between dest <=> origin weight
767		assert_ne!(value, Zero::zero());
768		// so the sum of unlocking chunks puts voter into the dest bag.
769		assert!(value * l.into() + origin_weight > origin_weight);
770		assert!(value * l.into() + origin_weight <= dest_weight);
771		let unlock_chunk = UnlockChunk::<BalanceOf<T>> { value, era: EraIndex::zero() };
772
773		let controller = scenario.origin_controller1;
774		let mut staking_ledger = Ledger::<T>::get(controller.clone()).unwrap();
775
776		for _ in 0..l {
777			staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap()
778		}
779		Ledger::<T>::insert(controller.clone(), staking_ledger.clone());
780		let original_bonded: BalanceOf<T> = staking_ledger.active;
781
782		whitelist_account!(controller);
783
784		#[extrinsic_call]
785		_(RawOrigin::Signed(controller.clone()), rebond_amount);
786
787		let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created after")?;
788		let new_bonded: BalanceOf<T> = ledger.active;
789		assert!(original_bonded < new_bonded);
790
791		Ok(())
792	}
793
794	#[benchmark]
795	fn reap_stash() -> Result<(), BenchmarkError> {
796		// clean up any existing state.
797		clear_validators_and_nominators::<T>();
798
799		let origin_weight = Staking::<T>::min_nominator_bond();
800
801		// setup a worst case list scenario. Note that we don't care about the setup of the
802		// destination position because we are doing a removal from the list but no insert.
803		let scenario = ListScenario::<T>::new(origin_weight, true)?;
804		let controller = scenario.origin_controller1.clone();
805		let stash = scenario.origin_stash1;
806
807		let l =
808			StakingLedger::<T>::new(stash.clone(), asset::existential_deposit::<T>() - One::one());
809		Ledger::<T>::insert(&controller, l);
810
811		assert!(Bonded::<T>::contains_key(&stash));
812		assert!(T::VoterList::contains(&stash));
813
814		whitelist_account!(controller);
815
816		#[extrinsic_call]
817		_(RawOrigin::Signed(controller), stash.clone(), 0);
818
819		assert!(!Bonded::<T>::contains_key(&stash));
820		assert!(!T::VoterList::contains(&stash));
821
822		Ok(())
823	}
824
825	#[benchmark]
826	fn set_staking_configs_all_set() {
827		#[extrinsic_call]
828		set_staking_configs(
829			RawOrigin::Root,
830			ConfigOp::Set(BalanceOf::<T>::max_value()),
831			ConfigOp::Set(BalanceOf::<T>::max_value()),
832			ConfigOp::Set(u32::MAX),
833			ConfigOp::Set(u32::MAX),
834			ConfigOp::Set(Percent::max_value()),
835			ConfigOp::Set(Perbill::max_value()),
836			ConfigOp::Set(Percent::max_value()),
837		);
838
839		assert_eq!(MinNominatorBond::<T>::get(), BalanceOf::<T>::max_value());
840		assert_eq!(MinValidatorBond::<T>::get(), BalanceOf::<T>::max_value());
841		assert_eq!(MaxNominatorsCount::<T>::get(), Some(u32::MAX));
842		assert_eq!(MaxValidatorsCount::<T>::get(), Some(u32::MAX));
843		assert_eq!(ChillThreshold::<T>::get(), Some(Percent::from_percent(100)));
844		assert_eq!(MinCommission::<T>::get(), Perbill::from_percent(100));
845		assert_eq!(MaxStakedRewards::<T>::get(), Some(Percent::from_percent(100)));
846	}
847
848	#[benchmark]
849	fn set_staking_configs_all_remove() {
850		#[extrinsic_call]
851		set_staking_configs(
852			RawOrigin::Root,
853			ConfigOp::Remove,
854			ConfigOp::Remove,
855			ConfigOp::Remove,
856			ConfigOp::Remove,
857			ConfigOp::Remove,
858			ConfigOp::Remove,
859			ConfigOp::Remove,
860		);
861
862		assert!(!MinNominatorBond::<T>::exists());
863		assert!(!MinValidatorBond::<T>::exists());
864		assert!(!MaxNominatorsCount::<T>::exists());
865		assert!(!MaxValidatorsCount::<T>::exists());
866		assert!(!ChillThreshold::<T>::exists());
867		assert!(!MinCommission::<T>::exists());
868		assert!(!MaxStakedRewards::<T>::exists());
869	}
870
871	#[benchmark]
872	fn chill_other() -> Result<(), BenchmarkError> {
873		// clean up any existing state.
874		clear_validators_and_nominators::<T>();
875
876		let origin_weight = Staking::<T>::min_nominator_bond();
877
878		// setup a worst case list scenario. Note that we don't care about the setup of the
879		// destination position because we are doing a removal from the list but no insert.
880		let scenario = ListScenario::<T>::new(origin_weight, true)?;
881		let stash = scenario.origin_stash1;
882		assert!(T::VoterList::contains(&stash));
883
884		Staking::<T>::set_staking_configs(
885			RawOrigin::Root.into(),
886			ConfigOp::Set(BalanceOf::<T>::max_value()),
887			ConfigOp::Set(BalanceOf::<T>::max_value()),
888			ConfigOp::Set(0),
889			ConfigOp::Set(0),
890			ConfigOp::Set(Percent::from_percent(0)),
891			ConfigOp::Set(Zero::zero()),
892			ConfigOp::Noop,
893		)?;
894
895		let caller = whitelisted_caller();
896
897		#[extrinsic_call]
898		_(RawOrigin::Signed(caller), stash.clone());
899
900		assert!(!T::VoterList::contains(&stash));
901
902		Ok(())
903	}
904
905	#[benchmark]
906	fn force_apply_min_commission() -> Result<(), BenchmarkError> {
907		// Clean up any existing state
908		clear_validators_and_nominators::<T>();
909
910		// Create a validator with a commission of 50%
911		let (stash, controller) = create_stash_controller::<T>(1, 1, RewardDestination::Staked)?;
912		let validator_prefs =
913			ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
914		Staking::<T>::validate(RawOrigin::Signed(controller).into(), validator_prefs)?;
915
916		// Sanity check
917		assert_eq!(
918			Validators::<T>::get(&stash),
919			ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }
920		);
921
922		// Set the min commission to 75%
923		MinCommission::<T>::set(Perbill::from_percent(75));
924		let caller = whitelisted_caller();
925
926		#[extrinsic_call]
927		_(RawOrigin::Signed(caller), stash.clone());
928
929		// The validators commission has been bumped to 75%
930		assert_eq!(
931			Validators::<T>::get(&stash),
932			ValidatorPrefs { commission: Perbill::from_percent(75), ..Default::default() }
933		);
934
935		Ok(())
936	}
937
938	#[benchmark]
939	fn set_min_commission() {
940		let min_commission = Perbill::max_value();
941
942		#[extrinsic_call]
943		_(RawOrigin::Root, min_commission);
944
945		assert_eq!(MinCommission::<T>::get(), Perbill::from_percent(100));
946	}
947
948	#[benchmark]
949	fn restore_ledger() -> Result<(), BenchmarkError> {
950		let (stash, controller) = create_stash_controller::<T>(0, 100, RewardDestination::Staked)?;
951		// corrupt ledger.
952		Ledger::<T>::remove(controller);
953
954		#[extrinsic_call]
955		_(RawOrigin::Root, stash.clone(), None, None, None);
956
957		assert_eq!(Staking::<T>::inspect_bond_state(&stash), Ok(LedgerIntegrityState::Ok));
958
959		Ok(())
960	}
961
962	#[benchmark]
963	fn migrate_currency() -> Result<(), BenchmarkError> {
964		let (stash, _ctrl) =
965			create_stash_controller::<T>(USER_SEED, 100, RewardDestination::Staked)?;
966		let stake = asset::staked::<T>(&stash);
967		migrate_to_old_currency::<T>(stash.clone());
968		// no holds
969		assert!(asset::staked::<T>(&stash).is_zero());
970		whitelist_account!(stash);
971
972		#[extrinsic_call]
973		_(RawOrigin::Signed(stash.clone()), stash.clone());
974
975		assert_eq!(asset::staked::<T>(&stash), stake);
976		Ok(())
977	}
978
979	#[benchmark]
980	fn apply_slash() -> Result<(), BenchmarkError> {
981		let era = EraIndex::one();
982		ActiveEra::<T>::put(ActiveEraInfo { index: era, start: None });
983		let (validator, nominators, _current_era) = create_validator_with_nominators::<T>(
984			T::MaxExposurePageSize::get() as u32,
985			T::MaxExposurePageSize::get() as u32,
986			false,
987			true,
988			RewardDestination::Staked,
989		)?;
990		let slash_fraction = Perbill::from_percent(10);
991		let page_index = 0;
992		let slashed_balance = BalanceOf::<T>::from(10u32);
993
994		let slash_key = (validator.clone(), slash_fraction, page_index);
995		let slashed_nominators =
996			nominators.iter().map(|(n, _)| (n.clone(), slashed_balance)).collect::<Vec<_>>();
997
998		let unapplied_slash = UnappliedSlash::<T> {
999			validator: validator.clone(),
1000			own: slashed_balance,
1001			others: WeakBoundedVec::force_from(slashed_nominators, None),
1002			reporter: Default::default(),
1003			payout: Zero::zero(),
1004		};
1005
1006		// Insert an unapplied slash to be processed.
1007		UnappliedSlashes::<T>::insert(era, slash_key.clone(), unapplied_slash);
1008
1009		#[extrinsic_call]
1010		_(RawOrigin::Signed(validator.clone()), era, slash_key.clone());
1011
1012		// Ensure the slash has been applied and removed.
1013		assert!(UnappliedSlashes::<T>::get(era, &slash_key).is_none());
1014
1015		Ok(())
1016	}
1017
1018	#[benchmark]
1019	fn process_offence_queue() -> Result<(), BenchmarkError> {
1020		// in tests, it is likely that `SlashDeferDuration` is zero and this will also insta-apply
1021		// the slash. Remove this just in case.
1022		#[cfg(test)]
1023		crate::mock::SlashDeferDuration::set(77);
1024
1025		// create at least one validator with a full page of exposure, as per `MaxExposurePageSize`.
1026		let all_validators = crate::testing_utils::create_validators_with_nominators_for_era::<T>(
1027			// we create more validators, but all of the nominators will back the first one
1028			ValidatorCount::<T>::get().max(1),
1029			// create two full exposure pages
1030			2 * T::MaxExposurePageSize::get(),
1031			16,
1032			false,
1033			Some(1),
1034		)?;
1035		let offender =
1036			T::Lookup::lookup(all_validators.first().cloned().expect("must exist")).unwrap();
1037
1038		// plan an era with this set
1039		let _new_validators = Rotator::<T>::legacy_insta_plan_era();
1040		// activate the previous one
1041		Rotator::<T>::start_era(
1042			crate::ActiveEraInfo { index: Rotator::<T>::planned_era() - 1, start: Some(1) },
1043			42, // start session index doesn't really matter,
1044			2,  // timestamp doesn't really matter
1045		);
1046
1047		// ensure our offender has at least a full exposure page
1048		let offender_exposure =
1049			Eras::<T>::get_full_exposure(Rotator::<T>::planned_era(), &offender);
1050		ensure!(
1051			offender_exposure.others.len() as u32 == 2 * T::MaxExposurePageSize::get(),
1052			"exposure not created"
1053		);
1054
1055		// create an offence for this validator
1056		let slash_session = 42;
1057		let offences = vec![rc_client::Offence {
1058			offender: offender.clone(),
1059			reporters: Default::default(),
1060			slash_fraction: Perbill::from_percent(50),
1061		}];
1062		<crate::Pallet<T> as rc_client::AHStakingInterface>::on_new_offences(
1063			slash_session,
1064			offences,
1065		);
1066
1067		// ensure offence is submitted
1068		ensure!(
1069			ValidatorSlashInEra::<T>::contains_key(Rotator::<T>::active_era(), offender),
1070			"offence not submitted"
1071		);
1072		ensure!(
1073			OffenceQueueEras::<T>::get().unwrap_or_default() == vec![Rotator::<T>::active_era()],
1074			"offence should be queued"
1075		);
1076
1077		#[block]
1078		{
1079			slashing::process_offence::<T>();
1080		}
1081
1082		ensure!(OffenceQueueEras::<T>::get().is_none(), "offence should not be queued");
1083
1084		Ok(())
1085	}
1086
1087	#[benchmark]
1088	fn rc_on_offence(
1089		v: Linear<2, { T::MaxValidatorSet::get() / 2 }>,
1090	) -> Result<(), BenchmarkError> {
1091		let initial_era = Rotator::<T>::planned_era();
1092		let _ = crate::testing_utils::create_validators_with_nominators_for_era::<T>(
1093			2 * v,
1094			// number of nominators is irrelevant here, so we hardcode these
1095			1000,
1096			16,
1097			false,
1098			None,
1099		)?;
1100
1101		// plan new era
1102		let new_validators = Rotator::<T>::legacy_insta_plan_era();
1103		ensure!(Rotator::<T>::planned_era() == initial_era + 1, "era should be incremented");
1104		// activate the previous one
1105		Rotator::<T>::start_era(
1106			crate::ActiveEraInfo { index: initial_era, start: Some(1) },
1107			42, // start session index doesn't really matter,
1108			2,  // timestamp doesn't really matter
1109		);
1110
1111		// this is needed in the slashing code, and is a sign that `initial_era + 1` is planned!
1112		ensure!(Rotator::<T>::active_era_start_session_index() == 42, "BondedEra not set");
1113
1114		// slash the first half of the validators
1115		let to_slash_count = new_validators.len() / 2;
1116		let to_slash = new_validators.into_iter().take(to_slash_count).collect::<Vec<_>>();
1117		let one_slashed = to_slash.first().cloned().unwrap();
1118		let offences = to_slash
1119			.into_iter()
1120			.map(|offender| rc_client::Offence {
1121				offender,
1122				reporters: Default::default(),
1123				slash_fraction: Perbill::from_percent(50),
1124			})
1125			.collect::<Vec<_>>();
1126		let slash_session = 42;
1127
1128		// has not pending slash for these guys now
1129		ensure!(
1130			!ValidatorSlashInEra::<T>::contains_key(initial_era + 1, &one_slashed),
1131			"offence submitted???"
1132		);
1133
1134		#[block]
1135		{
1136			<crate::Pallet<T> as rc_client::AHStakingInterface>::on_new_offences(
1137				slash_session,
1138				offences,
1139			);
1140		}
1141
1142		// ensure offence is recorded
1143		ensure!(
1144			ValidatorSlashInEra::<T>::contains_key(initial_era + 1, one_slashed),
1145			"offence not submitted"
1146		);
1147
1148		Ok(())
1149	}
1150
1151	#[benchmark]
1152	fn rc_on_session_report() -> Result<(), BenchmarkError> {
1153		let initial_planned_era = Rotator::<T>::planned_era();
1154		let initial_active_era = Rotator::<T>::active_era();
1155
1156		// create a small, arbitrary number of stakers. This is just for sanity of the era planning,
1157		// numbers don't matter.
1158		crate::testing_utils::create_validators_with_nominators_for_era::<T>(
1159			10, 50, 2, false, None,
1160		)?;
1161
1162		// plan new era
1163		let _new_validators = Rotator::<T>::legacy_insta_plan_era();
1164		ensure!(
1165			CurrentEra::<T>::get().unwrap() == initial_planned_era + 1,
1166			"era should be incremented"
1167		);
1168
1169		//  receive a session report with timestamp that actives the previous one.
1170		let validator_points = (0..T::MaxValidatorSet::get())
1171			.map(|v| (account::<T::AccountId>("random", v, SEED), v))
1172			.collect::<Vec<_>>();
1173		let activation_timestamp = Some((1u64, initial_planned_era + 1));
1174		let report = rc_client::SessionReport {
1175			end_index: 42,
1176			leftover: false,
1177			validator_points,
1178			activation_timestamp,
1179		};
1180
1181		#[block]
1182		{
1183			<crate::Pallet<T> as rc_client::AHStakingInterface>::on_relay_session_report(report);
1184		}
1185
1186		ensure!(Rotator::<T>::active_era() == initial_active_era + 1, "active era not bumped");
1187		Ok(())
1188	}
1189
1190	// Helper function to set up era data for pruning benchmarks
1191	fn setup_era_for_pruning<T: Config>(v: u32) -> EraIndex {
1192		let validators = v;
1193		let era = 7;
1194
1195		// Set active era to make era 7 prunable
1196		// Era is prunable if: era <= active_era - history_depth - 1
1197		let history_depth = T::HistoryDepth::get();
1198		let active_era = era + history_depth + 1;
1199		crate::ActiveEra::<T>::put(crate::ActiveEraInfo { index: active_era, start: Some(0) });
1200
1201		// Note: the number we are looking for here is not `MaxElectableVoters`, as these are unique
1202		// nominators. One unique nominator can be exposed behind multiple validators. The right
1203		// value is as follows:
1204		let max_total_nominators_per_validator =
1205			<T::ElectionProvider as ElectionProvider>::MaxBackersPerWinnerFinal::get();
1206		let exposed_nominators_per_validator = max_total_nominators_per_validator / validators;
1207
1208		// `ValidatorPrefs`
1209		for i in 0..validators {
1210			let validator = account::<T::AccountId>("validator", i, SEED);
1211			ErasValidatorPrefs::<T>::insert(era, validator.clone(), ValidatorPrefs::default())
1212		}
1213
1214		// `ClaimedRewards`
1215		let pages: WeakBoundedVec<_, _> = (0..crate::ClaimedRewardsBound::<T>::get())
1216			.collect::<Vec<_>>()
1217			.try_into()
1218			.unwrap();
1219		for i in 0..validators {
1220			let validator = account::<T::AccountId>("validator", i, SEED);
1221			ClaimedRewards::<T>::insert(era, validator.clone(), pages.clone())
1222		}
1223
1224		// `ErasStakersPaged` + `ErasStakersOverview`
1225		(0..validators)
1226			.map(|validator_index| account::<T::AccountId>("validator", validator_index, SEED))
1227			.for_each(|validator| {
1228				let exposure = sp_staking::Exposure::<T::AccountId, BalanceOf<T>> {
1229					own: BalanceOf::<T>::max_value(),
1230					total: BalanceOf::<T>::max_value(),
1231					others: (0..exposed_nominators_per_validator)
1232						.map(|n| {
1233							let nominator = account::<T::AccountId>("nominator", n, SEED);
1234							IndividualExposure {
1235								who: nominator,
1236								value: BalanceOf::<T>::max_value(),
1237							}
1238						})
1239						.collect::<Vec<_>>(),
1240				};
1241				Eras::<T>::upsert_exposure(era, &validator, exposure);
1242			});
1243
1244		// `ErasValidatorReward`
1245		ErasValidatorReward::<T>::insert(era, BalanceOf::<T>::max_value());
1246
1247		// `ErasRewardPoints`
1248		let reward_points = crate::EraRewardPoints::<T> {
1249			total: 77777,
1250			individual: (0..validators)
1251				.map(|v| account::<T::AccountId>("validator", v, SEED))
1252				.map(|v| (v, 7))
1253				.collect::<BTreeMap<_, _>>()
1254				.try_into()
1255				.unwrap(),
1256		};
1257		ErasRewardPoints::<T>::insert(era, reward_points);
1258
1259		// `ErasTotalStake`
1260		ErasTotalStake::<T>::insert(era, BalanceOf::<T>::max_value());
1261
1262		era
1263	}
1264
1265	/// Validates that the weight consumption of a pruning operation stays within expected limits.
1266	fn validate_pruning_weight<T: Config>(
1267		result: &frame_support::dispatch::DispatchResultWithPostInfo,
1268		step_name: &str,
1269		validator_count: u32,
1270	) {
1271		assert!(
1272			result.is_ok(),
1273			"Benchmark {} should succeed with v={}",
1274			step_name,
1275			validator_count
1276		);
1277
1278		let post_info = result.unwrap();
1279		let actual_ref_time = post_info
1280			.actual_weight
1281			.expect(&format!(
1282				"Should report actual weight for {} with v={}",
1283				step_name, validator_count
1284			))
1285			.ref_time();
1286
1287		assert!(
1288			actual_ref_time > 0,
1289			"Should report non-zero ref_time for {} with v={}",
1290			step_name,
1291			validator_count
1292		);
1293		// No need to validate against MaxPruningItems since we use item-based limiting
1294	}
1295
1296	// Benchmark pruning ErasStakersPaged (first step)
1297	#[benchmark(pov_mode = Measured)]
1298	fn prune_era_stakers_paged(
1299		v: Linear<1, { T::MaxValidatorSet::get() }>,
1300	) -> Result<(), BenchmarkError> {
1301		let era = setup_era_for_pruning::<T>(v);
1302		EraPruningState::<T>::insert(era, PruningStep::ErasStakersPaged);
1303
1304		let caller: T::AccountId = whitelisted_caller();
1305
1306		let result;
1307		#[block]
1308		{
1309			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1310		}
1311
1312		validate_pruning_weight::<T>(&result, "ErasStakersPaged", v);
1313
1314		Ok(())
1315	}
1316
1317	// Benchmark pruning ErasStakersOverview (second step)
1318	#[benchmark(pov_mode = Measured)]
1319	fn prune_era_stakers_overview(
1320		v: Linear<1, { T::MaxValidatorSet::get() }>,
1321	) -> Result<(), BenchmarkError> {
1322		let era = setup_era_for_pruning::<T>(v);
1323		EraPruningState::<T>::insert(era, PruningStep::ErasStakersOverview);
1324
1325		let caller: T::AccountId = whitelisted_caller();
1326
1327		let result;
1328		#[block]
1329		{
1330			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1331		}
1332
1333		validate_pruning_weight::<T>(&result, "ErasStakersOverview", v);
1334
1335		Ok(())
1336	}
1337
1338	// Benchmark pruning ErasValidatorPrefs (third step)
1339	#[benchmark(pov_mode = Measured)]
1340	fn prune_era_validator_prefs(
1341		v: Linear<1, { T::MaxValidatorSet::get() }>,
1342	) -> Result<(), BenchmarkError> {
1343		let era = setup_era_for_pruning::<T>(v);
1344		EraPruningState::<T>::insert(era, PruningStep::ErasValidatorPrefs);
1345
1346		let caller: T::AccountId = whitelisted_caller();
1347
1348		let result;
1349		#[block]
1350		{
1351			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1352		}
1353
1354		validate_pruning_weight::<T>(&result, "ErasValidatorPrefs", v);
1355
1356		Ok(())
1357	}
1358
1359	// Benchmark pruning ClaimedRewards (fourth step)
1360	#[benchmark(pov_mode = Measured)]
1361	fn prune_era_claimed_rewards(
1362		v: Linear<1, { T::MaxValidatorSet::get() }>,
1363	) -> Result<(), BenchmarkError> {
1364		let era = setup_era_for_pruning::<T>(v);
1365		EraPruningState::<T>::insert(era, PruningStep::ClaimedRewards);
1366
1367		let caller: T::AccountId = whitelisted_caller();
1368
1369		let result;
1370		#[block]
1371		{
1372			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1373		}
1374
1375		validate_pruning_weight::<T>(&result, "ClaimedRewards", v);
1376
1377		Ok(())
1378	}
1379
1380	// Benchmark pruning ErasValidatorReward (fifth step)
1381	#[benchmark(pov_mode = Measured)]
1382	fn prune_era_validator_reward() -> Result<(), BenchmarkError> {
1383		let era = setup_era_for_pruning::<T>(1);
1384		EraPruningState::<T>::insert(era, PruningStep::ErasValidatorReward);
1385
1386		let caller: T::AccountId = whitelisted_caller();
1387
1388		let result;
1389		#[block]
1390		{
1391			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1392		}
1393
1394		validate_pruning_weight::<T>(&result, "ErasValidatorReward", 1);
1395
1396		Ok(())
1397	}
1398
1399	// Benchmark pruning ErasRewardPoints (sixth step)
1400	#[benchmark(pov_mode = Measured)]
1401	fn prune_era_reward_points() -> Result<(), BenchmarkError> {
1402		let era = setup_era_for_pruning::<T>(1);
1403		EraPruningState::<T>::insert(era, PruningStep::ErasRewardPoints);
1404
1405		let caller: T::AccountId = whitelisted_caller();
1406
1407		let result;
1408		#[block]
1409		{
1410			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1411		}
1412
1413		validate_pruning_weight::<T>(&result, "ErasRewardPoints", 1);
1414
1415		Ok(())
1416	}
1417
1418	// Benchmark pruning ErasTotalStake (final step)
1419	#[benchmark(pov_mode = Measured)]
1420	fn prune_era_total_stake() -> Result<(), BenchmarkError> {
1421		let era = setup_era_for_pruning::<T>(1);
1422		EraPruningState::<T>::insert(era, PruningStep::ErasTotalStake);
1423
1424		let caller: T::AccountId = whitelisted_caller();
1425
1426		let result;
1427		#[block]
1428		{
1429			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1430		}
1431
1432		validate_pruning_weight::<T>(&result, "ErasTotalStake", 1);
1433
1434		Ok(())
1435	}
1436
1437	impl_benchmark_test_suite!(
1438		Staking,
1439		crate::mock::ExtBuilder::default().has_stakers(true),
1440		crate::mock::Test,
1441		exec_name = build_and_execute
1442	);
1443}
1444
1445#[cfg(test)]
1446mod tests {
1447	use super::*;
1448	use crate::mock::{ExtBuilder, RuntimeOrigin, Staking, Test};
1449	use frame_support::assert_ok;
1450
1451	#[test]
1452	fn create_validators_with_nominators_for_era_works() {
1453		ExtBuilder::default().build_and_execute(|| {
1454			let v = 10;
1455			let n = 100;
1456
1457			create_validators_with_nominators_for_era::<Test>(
1458				v,
1459				n,
1460				MaxNominationsOf::<Test>::get() as usize,
1461				false,
1462				None,
1463			)
1464			.unwrap();
1465
1466			let count_validators = Validators::<Test>::iter().count();
1467			let count_nominators = Nominators::<Test>::iter().count();
1468
1469			assert_eq!(count_validators, Validators::<Test>::count() as usize);
1470			assert_eq!(count_nominators, Nominators::<Test>::count() as usize);
1471
1472			assert_eq!(count_validators, v as usize);
1473			assert_eq!(count_nominators, n as usize);
1474		});
1475	}
1476
1477	#[test]
1478	fn create_validator_with_nominators_works() {
1479		ExtBuilder::default().build_and_execute(|| {
1480			let n = 10;
1481
1482			let (validator_stash, nominators, current_era) =
1483				create_validator_with_nominators::<Test>(
1484					n,
1485					<<Test as Config>::MaxExposurePageSize as Get<_>>::get(),
1486					false,
1487					false,
1488					RewardDestination::Staked,
1489				)
1490				.unwrap();
1491
1492			assert_eq!(nominators.len() as u32, n);
1493
1494			let original_stakeable_balance = asset::stakeable_balance::<Test>(&validator_stash);
1495			assert_ok!(Staking::payout_stakers_by_page(
1496				RuntimeOrigin::signed(1337),
1497				validator_stash,
1498				current_era,
1499				0
1500			));
1501			let new_stakeable_balance = asset::stakeable_balance::<Test>(&validator_stash);
1502
1503			// reward increases stakeable balance
1504			assert!(original_stakeable_balance < new_stakeable_balance);
1505		});
1506	}
1507}