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
1273		#[block]
1274		{
1275			<crate::Pallet<T> as rc_client::AHStakingInterface>::on_relay_session_report(report);
1276		}
1277
1278		ensure!(Rotator::<T>::active_era() == initial_active_era + 1, "active era not bumped");
1279		Ok(())
1280	}
1281
1282	// Helper function to set up era data for pruning benchmarks
1283	fn setup_era_for_pruning<T: Config>(v: u32) -> EraIndex {
1284		let validators = v;
1285		let era = 7;
1286
1287		// Set active era to make era 7 prunable
1288		// Era is prunable if: era <= active_era - history_depth - 1
1289		let history_depth = T::HistoryDepth::get();
1290		let active_era = era + history_depth + 1;
1291		crate::ActiveEra::<T>::put(crate::ActiveEraInfo { index: active_era, start: Some(0) });
1292
1293		let max_total_nominators_per_validator =
1294			<T::ElectionProvider as ElectionProvider>::MaxBackersPerWinnerFinal::get();
1295		let exposed_nominators_per_validator = max_total_nominators_per_validator / validators;
1296
1297		let pages: WeakBoundedVec<_, _> = (0..crate::ClaimedRewardsBound::<T>::get())
1298			.collect::<Vec<_>>()
1299			.try_into()
1300			.unwrap();
1301
1302		// 33% slashed — realistic worst-case under BFT assumptions.
1303		let slashed_validators = validators / 3;
1304
1305		let mut reward_points_individual = BTreeMap::new();
1306		let mut total_incentive_weight = BalanceOf::<T>::zero();
1307
1308		for i in 0..validators {
1309			let validator = account::<T::AccountId>("validator", i, SEED);
1310
1311			// ValidatorPrefs
1312			ErasValidatorPrefs::<T>::insert(era, validator.clone(), ValidatorPrefs::default());
1313
1314			// ClaimedRewards
1315			ClaimedRewards::<T>::insert(era, validator.clone(), pages.clone());
1316
1317			// ErasStakersPaged + ErasStakersOverview
1318			let exposure = sp_staking::Exposure::<T::AccountId, BalanceOf<T>> {
1319				own: T::Currency::minimum_balance(),
1320				total: T::Currency::minimum_balance() *
1321					(exposed_nominators_per_validator + 1).into(),
1322				others: (0..exposed_nominators_per_validator)
1323					.map(|n| {
1324						let nominator = account::<T::AccountId>("nominator", n, SEED);
1325						IndividualExposure { who: nominator, value: T::Currency::minimum_balance() }
1326					})
1327					.collect::<Vec<_>>(),
1328			};
1329			Eras::<T>::upsert_exposure(era, &validator, exposure);
1330
1331			// ErasRewardPoints (individual)
1332			reward_points_individual.insert(validator.clone(), 7u32);
1333
1334			// ValidatorSlashInEra (first 33%)
1335			if i < slashed_validators {
1336				crate::ValidatorSlashInEra::<T>::insert(
1337					era,
1338					validator.clone(),
1339					(Perbill::from_percent(10), BalanceOf::<T>::max_value() / 10u32.into()),
1340				);
1341			}
1342
1343			// ErasValidatorIncentiveWeight
1344			let incentive_weight = BalanceOf::<T>::from(100u64);
1345			ErasValidatorIncentiveWeight::<T>::insert(era, validator, incentive_weight);
1346			total_incentive_weight += incentive_weight;
1347		}
1348
1349		// Single-entry storages
1350		ErasValidatorReward::<T>::insert(era, BalanceOf::<T>::max_value());
1351		ErasRewardPoints::<T>::insert(
1352			era,
1353			crate::EraRewardPoints::<T> {
1354				total: 77777,
1355				individual: reward_points_individual.try_into().unwrap(),
1356			},
1357		);
1358		ErasTotalStake::<T>::insert(era, BalanceOf::<T>::max_value());
1359		ErasNominatorsSlashable::<T>::insert(era, true);
1360		ErasSumValidatorIncentiveWeight::<T>::insert(era, total_incentive_weight);
1361		ErasSumWeightedPoints::<T>::insert(
1362			era,
1363			total_incentive_weight.saturating_mul(BalanceOf::<T>::from(7u32)),
1364		);
1365		ErasValidatorIncentiveBudget::<T>::insert(era, BalanceOf::<T>::from(1_000_000u64));
1366
1367		era
1368	}
1369
1370	/// Validates that the weight consumption of a pruning operation stays within expected limits.
1371	fn validate_pruning_weight<T: Config>(
1372		result: &frame_support::dispatch::DispatchResultWithPostInfo,
1373		step_name: &str,
1374		validator_count: u32,
1375	) {
1376		assert!(
1377			result.is_ok(),
1378			"Benchmark {} should succeed with v={}",
1379			step_name,
1380			validator_count
1381		);
1382
1383		let post_info = result.unwrap();
1384		let actual_ref_time = post_info
1385			.actual_weight
1386			.expect(&format!(
1387				"Should report actual weight for {} with v={}",
1388				step_name, validator_count
1389			))
1390			.ref_time();
1391
1392		assert!(
1393			actual_ref_time > 0,
1394			"Should report non-zero ref_time for {} with v={}",
1395			step_name,
1396			validator_count
1397		);
1398		// No need to validate against MaxPruningItems since we use item-based limiting
1399	}
1400
1401	// Benchmark pruning ErasStakersPaged (first step)
1402	#[benchmark(pov_mode = Measured)]
1403	fn prune_era_stakers_paged(
1404		v: Linear<1, { T::MaxValidatorSet::get() }>,
1405	) -> Result<(), BenchmarkError> {
1406		let era = setup_era_for_pruning::<T>(v);
1407		EraPruningState::<T>::insert(era, PruningStep::ErasStakersPaged);
1408
1409		let caller: T::AccountId = whitelisted_caller();
1410
1411		let result;
1412		#[block]
1413		{
1414			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1415		}
1416
1417		validate_pruning_weight::<T>(&result, "ErasStakersPaged", v);
1418
1419		Ok(())
1420	}
1421
1422	// Benchmark pruning ErasStakersOverview (second step)
1423	#[benchmark(pov_mode = Measured)]
1424	fn prune_era_stakers_overview(
1425		v: Linear<1, { T::MaxValidatorSet::get() }>,
1426	) -> Result<(), BenchmarkError> {
1427		let era = setup_era_for_pruning::<T>(v);
1428		EraPruningState::<T>::insert(era, PruningStep::ErasStakersOverview);
1429
1430		let caller: T::AccountId = whitelisted_caller();
1431
1432		let result;
1433		#[block]
1434		{
1435			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1436		}
1437
1438		validate_pruning_weight::<T>(&result, "ErasStakersOverview", v);
1439
1440		Ok(())
1441	}
1442
1443	// Benchmark pruning ErasValidatorPrefs (third step)
1444	#[benchmark(pov_mode = Measured)]
1445	fn prune_era_validator_prefs(
1446		v: Linear<1, { T::MaxValidatorSet::get() }>,
1447	) -> Result<(), BenchmarkError> {
1448		let era = setup_era_for_pruning::<T>(v);
1449		EraPruningState::<T>::insert(era, PruningStep::ErasValidatorPrefs);
1450
1451		let caller: T::AccountId = whitelisted_caller();
1452
1453		let result;
1454		#[block]
1455		{
1456			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1457		}
1458
1459		validate_pruning_weight::<T>(&result, "ErasValidatorPrefs", v);
1460
1461		Ok(())
1462	}
1463
1464	// Benchmark pruning ClaimedRewards (fourth step)
1465	#[benchmark(pov_mode = Measured)]
1466	fn prune_era_claimed_rewards(
1467		v: Linear<1, { T::MaxValidatorSet::get() }>,
1468	) -> Result<(), BenchmarkError> {
1469		let era = setup_era_for_pruning::<T>(v);
1470		EraPruningState::<T>::insert(era, PruningStep::ClaimedRewards);
1471
1472		let caller: T::AccountId = whitelisted_caller();
1473
1474		let result;
1475		#[block]
1476		{
1477			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1478		}
1479
1480		validate_pruning_weight::<T>(&result, "ClaimedRewards", v);
1481
1482		Ok(())
1483	}
1484
1485	// Benchmark pruning ErasValidatorReward (fifth step)
1486	#[benchmark(pov_mode = Measured)]
1487	fn prune_era_validator_reward() -> Result<(), BenchmarkError> {
1488		let era = setup_era_for_pruning::<T>(1);
1489		EraPruningState::<T>::insert(era, PruningStep::ErasValidatorReward);
1490
1491		let caller: T::AccountId = whitelisted_caller();
1492
1493		let result;
1494		#[block]
1495		{
1496			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1497		}
1498
1499		validate_pruning_weight::<T>(&result, "ErasValidatorReward", 1);
1500
1501		Ok(())
1502	}
1503
1504	// Benchmark pruning ErasRewardPoints (sixth step)
1505	#[benchmark(pov_mode = Measured)]
1506	fn prune_era_reward_points() -> Result<(), BenchmarkError> {
1507		let era = setup_era_for_pruning::<T>(1);
1508		EraPruningState::<T>::insert(era, PruningStep::ErasRewardPoints);
1509
1510		let caller: T::AccountId = whitelisted_caller();
1511
1512		let result;
1513		#[block]
1514		{
1515			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1516		}
1517
1518		validate_pruning_weight::<T>(&result, "ErasRewardPoints", 1);
1519
1520		Ok(())
1521	}
1522
1523	// Benchmark pruning single-entry cleanups (seventh step)
1524	#[benchmark(pov_mode = Measured)]
1525	fn prune_era_single_entry_cleanups() -> Result<(), BenchmarkError> {
1526		let era = setup_era_for_pruning::<T>(1);
1527		EraPruningState::<T>::insert(era, PruningStep::SingleEntryCleanups);
1528
1529		let caller: T::AccountId = whitelisted_caller();
1530
1531		let result;
1532		#[block]
1533		{
1534			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1535		}
1536
1537		validate_pruning_weight::<T>(&result, "SingleEntryCleanups", 1);
1538
1539		Ok(())
1540	}
1541
1542	// Benchmark pruning ValidatorSlashInEra (eighth step)
1543	#[benchmark(pov_mode = Measured)]
1544	fn prune_era_validator_slash_in_era(
1545		v: Linear<1, { T::MaxValidatorSet::get() }>,
1546	) -> Result<(), BenchmarkError> {
1547		let era = setup_era_for_pruning::<T>(v);
1548		EraPruningState::<T>::insert(era, PruningStep::ValidatorSlashInEra);
1549
1550		let caller: T::AccountId = whitelisted_caller();
1551
1552		let result;
1553		#[block]
1554		{
1555			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1556		}
1557
1558		validate_pruning_weight::<T>(&result, "ValidatorSlashInEra", v);
1559
1560		Ok(())
1561	}
1562
1563	#[benchmark(pov_mode = Measured)]
1564	fn prune_era_validator_incentive_weight(
1565		v: Linear<1, { T::MaxValidatorSet::get() }>,
1566	) -> Result<(), BenchmarkError> {
1567		let era = setup_era_for_pruning::<T>(v);
1568		EraPruningState::<T>::insert(era, PruningStep::ErasValidatorIncentiveWeight);
1569
1570		let caller: T::AccountId = whitelisted_caller();
1571
1572		let result;
1573		#[block]
1574		{
1575			result = Pallet::<T>::prune_era_step(RawOrigin::Signed(caller).into(), era);
1576		}
1577
1578		validate_pruning_weight::<T>(&result, "ErasValidatorIncentiveWeight", v);
1579
1580		Ok(())
1581	}
1582
1583	#[benchmark(pov_mode = Measured)]
1584	fn chill_inactive(
1585		l: Linear<2, { ChillInactiveThreshold::<T>::get() }>,
1586	) -> Result<(), BenchmarkError> {
1587		let (stash, _, _) =
1588			create_validator_with_nominators::<T>(0, 0, false, true, RewardDestination::Staked)?;
1589		assert!(T::VoterList::contains(&stash));
1590
1591		Staking::<T>::set_staking_configs(
1592			RawOrigin::Root.into(),
1593			ConfigOp::Set(BalanceOf::<T>::max_value()),
1594			ConfigOp::Set(BalanceOf::<T>::max_value()),
1595			ConfigOp::Set(0),
1596			ConfigOp::Set(0),
1597			ConfigOp::Set(Percent::from_percent(0)),
1598			ConfigOp::Set(Zero::zero()),
1599			ConfigOp::Noop,
1600			ConfigOp::Noop,
1601			ConfigOp::Set(l),
1602		)?;
1603
1604		let caller = whitelisted_caller();
1605
1606		// Proof eras `0..l` must sit in `[active_era - HistoryDepth, active_era)`. `l <=
1607		// HistoryDepth`, so `active_era = l` keeps the lower bound at 0 while the most recent
1608		// proof era (`l - 1`) stays below the active era.
1609		set_active_era::<T>(l);
1610
1611		// Set the validator has been inactive for `l` eras.
1612		let proof = (0..l)
1613			.inspect(|&era| {
1614				ErasRewardPoints::<T>::insert(
1615					era,
1616					EraRewardPoints { total: 0, individual: BoundedBTreeMap::new() },
1617				);
1618				Eras::<T>::upsert_exposure(era, &stash, Exposure::default());
1619			})
1620			.collect::<Vec<_>>();
1621		let proof = BoundedVec::truncate_from(proof);
1622
1623		assert!(Validators::<T>::contains_key(&stash));
1624
1625		#[extrinsic_call]
1626		_(RawOrigin::Signed(caller), stash.clone(), proof);
1627
1628		assert!(!Validators::<T>::contains_key(&stash));
1629
1630		Ok(())
1631	}
1632
1633	impl_benchmark_test_suite!(
1634		Staking,
1635		crate::mock::ExtBuilder::default().has_stakers(true),
1636		crate::mock::Test,
1637		exec_name = build_and_execute
1638	);
1639}
1640
1641#[cfg(test)]
1642mod tests {
1643	use super::*;
1644	use crate::mock::{ExtBuilder, RuntimeOrigin, Staking, Test};
1645	use frame_support::assert_ok;
1646
1647	#[test]
1648	fn create_validators_with_nominators_for_era_works() {
1649		ExtBuilder::default().build_and_execute(|| {
1650			let v = 10;
1651			let n = 100;
1652
1653			create_validators_with_nominators_for_era::<Test>(
1654				v,
1655				n,
1656				MaxNominationsOf::<Test>::get() as usize,
1657				false,
1658				None,
1659			)
1660			.unwrap();
1661
1662			let count_validators = Validators::<Test>::iter().count();
1663			let count_nominators = Nominators::<Test>::iter().count();
1664
1665			assert_eq!(count_validators, Validators::<Test>::count() as usize);
1666			assert_eq!(count_nominators, Nominators::<Test>::count() as usize);
1667
1668			assert_eq!(count_validators, v as usize);
1669			assert_eq!(count_nominators, n as usize);
1670		});
1671	}
1672
1673	#[test]
1674	fn create_validator_with_nominators_works() {
1675		ExtBuilder::default().build_and_execute(|| {
1676			let n = 10;
1677
1678			let (validator_stash, nominators, current_era) =
1679				create_validator_with_nominators::<Test>(
1680					n,
1681					<<Test as Config>::MaxExposurePageSize as Get<_>>::get(),
1682					false,
1683					false,
1684					RewardDestination::Staked,
1685				)
1686				.unwrap();
1687
1688			assert_eq!(nominators.len() as u32, n);
1689
1690			let original_stakeable_balance = asset::stakeable_balance::<Test>(&validator_stash);
1691			assert_ok!(Staking::payout_stakers_by_page(
1692				RuntimeOrigin::signed(1337),
1693				validator_stash,
1694				current_era,
1695				0
1696			));
1697			let new_stakeable_balance = asset::stakeable_balance::<Test>(&validator_stash);
1698
1699			// reward increases stakeable balance
1700			assert!(original_stakeable_balance < new_stakeable_balance);
1701		});
1702	}
1703}