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,
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
48// This function clears all existing validators and nominators from the set, and generates one new
49// validator being nominated by n nominators, and returns the validator stash account and the
50// nominators' stash and controller. It also starts plans a new era with this new stakers, and
51// returns the planned era index.
52pub(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	// TODO: this can be replaced with `testing_utils` version?
60	// Clean up any existing state.
61	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	// Give the validator n nominators, but keep total users in the system the same.
83	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	// Start a new Era
101	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	// Give Era Points
114	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	// Create reward pool
122	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	/// Stash that is expected to be moved.
132	origin_stash1: T::AccountId,
133	/// Controller of the Stash that is expected to be moved.
134	origin_controller1: T::AccountId,
135	dest_weight: BalanceOf<T>,
136}
137
138impl<T: Config> ListScenario<T> {
139	/// An expensive scenario for bags-list implementation:
140	///
141	/// - the node to be updated (r) is the head of a bag that has at least one other node. The bag
142	///   itself will need to be read and written to update its head. The node pointed to by r.next
143	///   will need to be read and written as it will need to have its prev pointer updated. Note
144	///   that there are two other worst case scenarios for bag removal: 1) the node is a tail and
145	///   2) the node is a middle node with prev and next; all scenarios end up with the same number
146	///   of storage reads and writes.
147	///
148	/// - the destination bag has at least one node, which will need its next pointer updated.
149	///
150	/// NOTE: while this scenario specifically targets a worst case for the bags-list, it should
151	/// also elicit a worst case for other known `VoterList` implementations; although
152	/// this may not be true against unknown `VoterList` implementations.
153	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		// burn the entire issuance.
157		let i = asset::burn::<T>(asset::total_issuance::<T>());
158		core::mem::forget(i);
159
160		// make sure `account("random_validator", 0, SEED)` is a validator
161		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		// create accounts with the origin weight
175		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			// NOTE: these don't really need to be validators.
183			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		// find a destination weight that will trigger the worst case scenario
197		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		// create an account with the worst case destination weight
206		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		// clean up any existing state.
244		clear_validators_and_nominators::<T>();
245
246		let origin_weight = Staking::<T>::min_nominator_bond();
247
248		// setup the worst case list scenario.
249
250		// the weight the nominator will start at.
251		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		// clean up any existing state.
282		clear_validators_and_nominators::<T>();
283
284		// the weight the nominator will start at. The value used here is expected to be
285		// significantly higher than the first position in a list (e.g. the first bag threshold).
286		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	// Withdraw only updates the ledger
310	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(); // Half of total
313		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	// Worst case scenario, everything is removed after the bonding duration
331	fn withdraw_unbonded_kill() -> Result<(), BenchmarkError> {
332		// clean up any existing state.
333		clear_validators_and_nominators::<T>();
334
335		let origin_weight = Staking::<T>::min_nominator_bond();
336
337		// setup a worst case list scenario. Note that we don't care about the setup of the
338		// destination position because we are doing a removal from the list but no insert.
339		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		// because it is chilled.
369		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		// scenario: we want to kick `k` nominators from nominating us (we are a validator).
386		// we'll assume that `k` is under 128 for the purposes of determining the slope.
387		// each nominator should have `T::MaxNominations::get()` validators nominated, and our
388		// validator should be somewhere in there.
389		k: Linear<1, 128>,
390	) -> Result<(), BenchmarkError> {
391		// these are the other validators; there are `T::MaxNominations::get() - 1` of them, so
392		// there are a total of `T::MaxNominations::get()` validators in the system.
393		let rest_of_validators =
394			create_validators_with_seed::<T>(MaxNominationsOf::<T>::get() - 1, 100, 415)?;
395
396		// this is the validator that will be kicking.
397		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		// they start validating.
405		Staking::<T>::validate(RawOrigin::Signed(controller.clone()).into(), Default::default())?;
406
407		// we now create the nominators. there will be `k` of them; each will nominate all
408		// validators. we will then kick each of the `k` nominators from the main validator.
409		let mut nominator_stashes = Vec::with_capacity(k as usize);
410		for i in 0..k {
411			// create a nominator stash.
412			let (n_stash, n_controller) = create_stash_controller::<T>(
413				MaxNominationsOf::<T>::get() + i,
414				100,
415				RewardDestination::Staked,
416			)?;
417
418			// bake the nominations; we first clone them from the rest of the validators.
419			let mut nominations = rest_of_validators.clone();
420			// then insert "our" validator somewhere in there (we vary it) to avoid accidental
421			// optimisations/pessimisations.
422			nominations.insert(i as usize % (nominations.len() + 1), stash_lookup.clone());
423			// then we nominate.
424			Staking::<T>::nominate(RawOrigin::Signed(n_controller.clone()).into(), nominations)?;
425
426			nominator_stashes.push(n_stash);
427		}
428
429		// all nominators now should be nominating our validator...
430		for n in nominator_stashes.iter() {
431			assert!(Nominators::<T>::get(n).unwrap().targets.contains(&stash));
432		}
433
434		// we need the unlookuped version of the nominator stash for the kick.
435		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		// all nominators now should *not* be nominating our validator...
446		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	// Worst case scenario, T::MaxNominations::get()
455	fn nominate(n: Linear<1, { MaxNominationsOf::<T>::get() }>) -> Result<(), BenchmarkError> {
456		// clean up any existing state.
457		clear_validators_and_nominators::<T>();
458
459		let origin_weight = Staking::<T>::min_nominator_bond();
460
461		// setup a worst case list scenario. Note we don't care about the destination position,
462		// because we are just doing an insert into the origin position.
463		ListScenario::<T>::new(origin_weight, true)?;
464		let (stash, controller) = create_stash_controller_with_balance::<T>(
465			SEED + MaxNominationsOf::<T>::get() + 1, /* make sure the account does not conflict
466			                                          * with others */
467			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		// clean up any existing state.
490		clear_validators_and_nominators::<T>();
491
492		let origin_weight = Staking::<T>::min_nominator_bond();
493
494		// setup a worst case list scenario. Note that we don't care about the setup of the
495		// destination position because we are doing a removal from the list but no insert.
496		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		// ensure `ctlr` is the currently stored controller.
549		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		// We pass a dynamic number of controllers to the benchmark, up to
600		// `MaxControllersInDeprecationBatch`.
601		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			// Ledger no longer keyed by controller.
621			assert_eq!(Ledger::<T>::get(controller), None);
622			// Bonded now maps to the stash.
623			assert_eq!(Bonded::<T>::get(stash), Some(stash.clone()));
624			// Ledger is now keyed by stash.
625			assert_eq!(Ledger::<T>::get(stash).unwrap().stash, *stash);
626		}
627
628		Ok(())
629	}
630
631	#[benchmark]
632	fn force_unstake() -> Result<(), BenchmarkError> {
633		// Clean up any existing state.
634		clear_validators_and_nominators::<T>();
635
636		let origin_weight = Staking::<T>::min_nominator_bond();
637
638		// setup a worst case list scenario. Note that we don't care about the setup of the
639		// destination position because we are doing a removal from the list but no insert.
640		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		// Create validators and insert slashes
659		let validators: Vec<_> = (0..s)
660			.map(|i| {
661				let validator: T::AccountId = account("validator", i, SEED);
662
663				// Insert slash for this validator
664				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		// Convert validators to tuples with 10% slash fraction (matching the slashes created above)
679		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		// Ensure cancelled slashes are stored correctly
686		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		// set the commission for this particular era as well.
703		<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		// clean up any existing state.
740		clear_validators_and_nominators::<T>();
741
742		let origin_weight = Pallet::<T>::min_nominator_bond()
743			// we use 100 to play friendly with the list threshold values in the mock
744			.max(100u32.into());
745
746		// setup a worst case list scenario.
747		let scenario = ListScenario::<T>::new(origin_weight, true)?;
748		let dest_weight = scenario.dest_weight;
749
750		// rebond an amount that will give the user dest_weight
751		let rebond_amount = dest_weight - origin_weight;
752
753		// spread that amount to rebond across `l` unlocking chunks,
754		let value = rebond_amount / l.into();
755		// if `value` is zero, we need a greater delta between dest <=> origin weight
756		assert_ne!(value, Zero::zero());
757		// so the sum of unlocking chunks puts voter into the dest bag.
758		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		// clean up any existing state.
786		clear_validators_and_nominators::<T>();
787
788		let origin_weight = Staking::<T>::min_nominator_bond();
789
790		// setup a worst case list scenario. Note that we don't care about the setup of the
791		// destination position because we are doing a removal from the list but no insert.
792		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		// clean up any existing state.
863		clear_validators_and_nominators::<T>();
864
865		let origin_weight = Staking::<T>::min_nominator_bond();
866
867		// setup a worst case list scenario. Note that we don't care about the setup of the
868		// destination position because we are doing a removal from the list but no insert.
869		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		// Clean up any existing state
897		clear_validators_and_nominators::<T>();
898
899		// Create a validator with a commission of 50%
900		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		// Sanity check
906		assert_eq!(
907			Validators::<T>::get(&stash),
908			ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }
909		);
910
911		// Set the min commission to 75%
912		MinCommission::<T>::set(Perbill::from_percent(75));
913		let caller = whitelisted_caller();
914
915		#[extrinsic_call]
916		_(RawOrigin::Signed(caller), stash.clone());
917
918		// The validators commission has been bumped to 75%
919		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		// corrupt ledger.
941		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		// no holds
958		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		// Insert an unapplied slash to be processed.
996		UnappliedSlashes::<T>::insert(era, slash_key.clone(), unapplied_slash);
997
998		#[extrinsic_call]
999		_(RawOrigin::Signed(validator.clone()), era, slash_key.clone());
1000
1001		// Ensure the slash has been applied and removed.
1002		assert!(UnappliedSlashes::<T>::get(era, &slash_key).is_none());
1003
1004		Ok(())
1005	}
1006
1007	#[benchmark]
1008	fn process_offence_queue() -> Result<(), BenchmarkError> {
1009		// in tests, it is likely that `SlashDeferDuration` is zero and this will also insta-apply
1010		// the slash. Remove this just in case.
1011		#[cfg(test)]
1012		crate::mock::SlashDeferDuration::set(77);
1013
1014		// create at least one validator with a full page of exposure, as per `MaxExposurePageSize`.
1015		let all_validators = crate::testing_utils::create_validators_with_nominators_for_era::<T>(
1016			// we create more validators, but all of the nominators will back the first one
1017			ValidatorCount::<T>::get().max(1),
1018			// create two full exposure pages
1019			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		// plan an era with this set
1028		let _new_validators = Rotator::<T>::legacy_insta_plan_era();
1029		// activate the previous one
1030		Rotator::<T>::start_era(
1031			crate::ActiveEraInfo { index: Rotator::<T>::planned_era() - 1, start: Some(1) },
1032			42, // start session index doesn't really matter,
1033			2,  // timestamp doesn't really matter
1034		);
1035
1036		// ensure our offender has at least a full exposure page
1037		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		// create an offence for this validator
1045		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 offence is submitted
1057		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			// number of nominators is irrelevant here, so we hardcode these
1084			1000,
1085			16,
1086			false,
1087			None,
1088		)?;
1089
1090		// plan new era
1091		let new_validators = Rotator::<T>::legacy_insta_plan_era();
1092		ensure!(Rotator::<T>::planned_era() == initial_era + 1, "era should be incremented");
1093		// activate the previous one
1094		Rotator::<T>::start_era(
1095			crate::ActiveEraInfo { index: initial_era, start: Some(1) },
1096			42, // start session index doesn't really matter,
1097			2,  // timestamp doesn't really matter
1098		);
1099
1100		// this is needed in the slashing code, and is a sign that `initial_era + 1` is planned!
1101		ensure!(Rotator::<T>::active_era_start_session_index() == 42, "BondedEra not set");
1102
1103		// slash the first half of the validators
1104		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		// has not pending slash for these guys now
1118		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 offence is recorded
1132		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		// create a small, arbitrary number of stakers. This is just for sanity of the era planning,
1146		// numbers don't matter.
1147		crate::testing_utils::create_validators_with_nominators_for_era::<T>(
1148			10, 50, 2, false, None,
1149		)?;
1150
1151		// plan new era
1152		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		//  receive a session report with timestamp that actives the previous one.
1159		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	// Helper function to set up era data for pruning benchmarks
1180	fn setup_era_for_pruning<T: Config>(v: u32) -> EraIndex {
1181		let validators = v;
1182		let era = 7;
1183
1184		// Set active era to make era 7 prunable
1185		// Era is prunable if: era <= active_era - history_depth - 1
1186		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		// Note: the number we are looking for here is not `MaxElectableVoters`, as these are unique
1191		// nominators. One unique nominator can be exposed behind multiple validators. The right
1192		// value is as follows:
1193		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		// `ValidatorPrefs`
1198		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		// `ClaimedRewards`
1204		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		// `ErasStakersPaged` + `ErasStakersOverview`
1214		(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`
1235		ErasValidatorReward::<T>::insert(era, BalanceOf::<T>::max_value());
1236
1237		// `ErasRewardPoints`
1238		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`
1250		ErasTotalStake::<T>::insert(era, BalanceOf::<T>::max_value());
1251
1252		era
1253	}
1254
1255	/// Validates that the weight consumption of a pruning operation stays within expected limits.
1256	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		// No need to validate against MaxPruningItems since we use item-based limiting
1284	}
1285
1286	// Benchmark pruning ErasStakersPaged (first step)
1287	#[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 pruning ErasStakersOverview (second step)
1308	#[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 pruning ErasValidatorPrefs (third step)
1329	#[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 pruning ClaimedRewards (fourth step)
1350	#[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 pruning ErasValidatorReward (fifth step)
1371	#[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 pruning ErasRewardPoints (sixth step)
1390	#[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 pruning ErasTotalStake (final step)
1409	#[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			// reward increases stakeable balance
1494			assert!(original_stakeable_balance < new_stakeable_balance);
1495		});
1496	}
1497}