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