referrerpolicy=no-referrer-when-downgrade

pallet_staking/
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::{asset, ConfigOp, Pallet as Staking};
22use testing_utils::*;
23
24use codec::Decode;
25use frame_election_provider_support::{bounds::DataProviderBounds, SortedListProvider};
26use frame_support::{
27	pallet_prelude::*,
28	storage::bounded_vec::BoundedVec,
29	traits::{Get, Imbalance, UnfilteredDispatchable},
30};
31use sp_runtime::{
32	traits::{Bounded, One, StaticLookup, TrailingZeroInput, Zero},
33	Perbill, Percent, Saturating,
34};
35use sp_staking::{currency_to_vote::CurrencyToVote, SessionIndex};
36
37pub use frame_benchmarking::{
38	impl_benchmark_test_suite, v2::*, whitelist_account, whitelisted_caller, BenchmarkError,
39};
40use frame_system::RawOrigin;
41
42const SEED: u32 = 0;
43const MAX_SPANS: u32 = 100;
44const MAX_SLASHES: u32 = 1000;
45
46type MaxValidators<T> = <<T as Config>::BenchmarkingConfig as BenchmarkingConfig>::MaxValidators;
47type MaxNominators<T> = <<T as Config>::BenchmarkingConfig as BenchmarkingConfig>::MaxNominators;
48
49// Add slashing spans to a user account. Not relevant for actual use, only to benchmark
50// read and write operations.
51pub fn add_slashing_spans<T: Config>(who: &T::AccountId, spans: u32) {
52	if spans == 0 {
53		return
54	}
55
56	// For the first slashing span, we initialize
57	let mut slashing_spans = crate::slashing::SlashingSpans::new(0);
58	SpanSlash::<T>::insert((who, 0), crate::slashing::SpanRecord::default());
59
60	for i in 1..spans {
61		assert!(slashing_spans.end_span(i));
62		SpanSlash::<T>::insert((who, i), crate::slashing::SpanRecord::default());
63	}
64	SlashingSpans::<T>::insert(who, slashing_spans);
65}
66
67// This function clears all existing validators and nominators from the set, and generates one new
68// validator being nominated by n nominators, and returns the validator stash account and the
69// nominators' stash and controller. It also starts an era and creates pending payouts.
70pub fn create_validator_with_nominators<T: Config>(
71	n: u32,
72	upper_bound: u32,
73	dead_controller: bool,
74	unique_controller: bool,
75	destination: RewardDestination<T::AccountId>,
76) -> Result<(T::AccountId, Vec<(T::AccountId, T::AccountId)>), &'static str> {
77	// Clean up any existing state.
78	clear_validators_and_nominators::<T>();
79	let mut points_total = 0;
80	let mut points_individual = Vec::new();
81
82	let (v_stash, v_controller) = if unique_controller {
83		create_unique_stash_controller::<T>(0, 100, destination.clone(), false)?
84	} else {
85		create_stash_controller::<T>(0, 100, destination.clone())?
86	};
87
88	let validator_prefs =
89		ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
90	Staking::<T>::validate(RawOrigin::Signed(v_controller).into(), validator_prefs)?;
91	let stash_lookup = T::Lookup::unlookup(v_stash.clone());
92
93	points_total += 10;
94	points_individual.push((v_stash.clone(), 10));
95
96	let original_nominator_count = Nominators::<T>::count();
97	let mut nominators = Vec::new();
98
99	// Give the validator n nominators, but keep total users in the system the same.
100	for i in 0..upper_bound {
101		let (n_stash, n_controller) = if !dead_controller {
102			create_stash_controller::<T>(u32::MAX - i, 100, destination.clone())?
103		} else {
104			create_unique_stash_controller::<T>(u32::MAX - i, 100, destination.clone(), true)?
105		};
106		if i < n {
107			Staking::<T>::nominate(
108				RawOrigin::Signed(n_controller.clone()).into(),
109				vec![stash_lookup.clone()],
110			)?;
111			nominators.push((n_stash, n_controller));
112		}
113	}
114
115	ValidatorCount::<T>::put(1);
116
117	// Start a new Era
118	let new_validators = Staking::<T>::try_trigger_new_era(SessionIndex::one(), true).unwrap();
119
120	assert_eq!(new_validators.len(), 1);
121	assert_eq!(new_validators[0], v_stash, "Our validator was not selected!");
122	assert_ne!(Validators::<T>::count(), 0);
123	assert_eq!(Nominators::<T>::count(), original_nominator_count + nominators.len() as u32);
124
125	// Give Era Points
126	let reward = EraRewardPoints::<T::AccountId> {
127		total: points_total,
128		individual: points_individual.into_iter().collect(),
129	};
130
131	let current_era = CurrentEra::<T>::get().unwrap();
132	ErasRewardPoints::<T>::insert(current_era, reward);
133
134	// Create reward pool
135	let total_payout = asset::existential_deposit::<T>()
136		.saturating_mul(upper_bound.into())
137		.saturating_mul(1000u32.into());
138	<ErasValidatorReward<T>>::insert(current_era, total_payout);
139
140	Ok((v_stash, nominators))
141}
142
143struct ListScenario<T: Config> {
144	/// Stash that is expected to be moved.
145	origin_stash1: T::AccountId,
146	/// Controller of the Stash that is expected to be moved.
147	origin_controller1: T::AccountId,
148	dest_weight: BalanceOf<T>,
149}
150
151impl<T: Config> ListScenario<T> {
152	/// An expensive scenario for bags-list implementation:
153	///
154	/// - the node to be updated (r) is the head of a bag that has at least one other node. The bag
155	///   itself will need to be read and written to update its head. The node pointed to by r.next
156	///   will need to be read and written as it will need to have its prev pointer updated. Note
157	///   that there are two other worst case scenarios for bag removal: 1) the node is a tail and
158	///   2) the node is a middle node with prev and next; all scenarios end up with the same number
159	///   of storage reads and writes.
160	///
161	/// - the destination bag has at least one node, which will need its next pointer updated.
162	///
163	/// NOTE: while this scenario specifically targets a worst case for the bags-list, it should
164	/// also elicit a worst case for other known `VoterList` implementations; although
165	/// this may not be true against unknown `VoterList` implementations.
166	fn new(origin_weight: BalanceOf<T>, is_increase: bool) -> Result<Self, &'static str> {
167		ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0");
168
169		// burn the entire issuance.
170		let i = asset::burn::<T>(asset::total_issuance::<T>());
171		core::mem::forget(i);
172
173		// create accounts with the origin weight
174
175		let (origin_stash1, origin_controller1) = create_stash_controller_with_balance::<T>(
176			USER_SEED + 2,
177			origin_weight,
178			RewardDestination::Staked,
179		)?;
180		Staking::<T>::nominate(
181			RawOrigin::Signed(origin_controller1.clone()).into(),
182			// NOTE: these don't really need to be validators.
183			vec![T::Lookup::unlookup(account("random_validator", 0, SEED))],
184		)?;
185
186		let (_origin_stash2, origin_controller2) = create_stash_controller_with_balance::<T>(
187			USER_SEED + 3,
188			origin_weight,
189			RewardDestination::Staked,
190		)?;
191		Staking::<T>::nominate(
192			RawOrigin::Signed(origin_controller2).into(),
193			vec![T::Lookup::unlookup(account("random_validator", 0, SEED))],
194		)?;
195
196		// find a destination weight that will trigger the worst case scenario
197		let dest_weight_as_vote =
198			T::VoterList::score_update_worst_case(&origin_stash1, is_increase);
199
200		let total_issuance = asset::total_issuance::<T>();
201
202		let dest_weight =
203			T::CurrencyToVote::to_currency(dest_weight_as_vote as u128, total_issuance);
204
205		// create an account with the worst case destination weight
206		let (_dest_stash1, dest_controller1) = create_stash_controller_with_balance::<T>(
207			USER_SEED + 1,
208			dest_weight,
209			RewardDestination::Staked,
210		)?;
211		Staking::<T>::nominate(
212			RawOrigin::Signed(dest_controller1).into(),
213			vec![T::Lookup::unlookup(account("random_validator", 0, SEED))],
214		)?;
215
216		Ok(ListScenario { origin_stash1, origin_controller1, dest_weight })
217	}
218}
219
220const USER_SEED: u32 = 999666;
221
222#[benchmarks]
223mod benchmarks {
224	use super::*;
225
226	#[benchmark]
227	fn bond() {
228		let stash = create_funded_user::<T>("stash", USER_SEED, 100);
229		let reward_destination = RewardDestination::Staked;
230		let amount = asset::existential_deposit::<T>() * 10u32.into();
231		whitelist_account!(stash);
232
233		#[extrinsic_call]
234		_(RawOrigin::Signed(stash.clone()), amount, reward_destination);
235
236		assert!(Bonded::<T>::contains_key(stash.clone()));
237		assert!(Ledger::<T>::contains_key(stash));
238	}
239
240	#[benchmark]
241	fn bond_extra() -> Result<(), BenchmarkError> {
242		// clean up any existing state.
243		clear_validators_and_nominators::<T>();
244
245		let origin_weight = MinNominatorBond::<T>::get().max(asset::existential_deposit::<T>());
246
247		// setup the worst case list scenario.
248
249		// the weight the nominator will start at.
250		let scenario = ListScenario::<T>::new(origin_weight, true)?;
251
252		let max_additional = scenario.dest_weight - origin_weight;
253
254		let stash = scenario.origin_stash1.clone();
255		let controller = scenario.origin_controller1;
256		let original_bonded: BalanceOf<T> = Ledger::<T>::get(&controller)
257			.map(|l| l.active)
258			.ok_or("ledger not created after")?;
259
260		let _ = asset::mint_into_existing::<T>(
261			&stash,
262			max_additional + asset::existential_deposit::<T>(),
263		)
264		.unwrap();
265
266		whitelist_account!(stash);
267
268		#[extrinsic_call]
269		_(RawOrigin::Signed(stash), max_additional);
270
271		let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created after")?;
272		let new_bonded: BalanceOf<T> = ledger.active;
273		assert!(original_bonded < new_bonded);
274
275		Ok(())
276	}
277
278	#[benchmark]
279	fn unbond() -> Result<(), BenchmarkError> {
280		// clean up any existing state.
281		clear_validators_and_nominators::<T>();
282
283		// the weight the nominator will start at. The value used here is expected to be
284		// significantly higher than the first position in a list (e.g. the first bag threshold).
285		let origin_weight = BalanceOf::<T>::try_from(952_994_955_240_703u128)
286			.map_err(|_| "balance expected to be a u128")
287			.unwrap();
288		let scenario = ListScenario::<T>::new(origin_weight, false)?;
289
290		let controller = scenario.origin_controller1.clone();
291		let amount = origin_weight - scenario.dest_weight;
292		let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created before")?;
293		let original_bonded: BalanceOf<T> = ledger.active;
294
295		whitelist_account!(controller);
296
297		#[extrinsic_call]
298		_(RawOrigin::Signed(controller.clone()), amount);
299
300		let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created after")?;
301		let new_bonded: BalanceOf<T> = ledger.active;
302		assert!(original_bonded > new_bonded);
303
304		Ok(())
305	}
306
307	#[benchmark]
308	// Withdraw only updates the ledger
309	fn withdraw_unbonded_update(
310		// Slashing Spans
311		s: Linear<0, MAX_SPANS>,
312	) -> Result<(), BenchmarkError> {
313		let (stash, controller) = create_stash_controller::<T>(0, 100, RewardDestination::Staked)?;
314		add_slashing_spans::<T>(&stash, s);
315		let amount = asset::existential_deposit::<T>() * 5u32.into(); // Half of total
316		Staking::<T>::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?;
317		CurrentEra::<T>::put(EraIndex::max_value());
318		let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created before")?;
319		let original_total: BalanceOf<T> = ledger.total;
320		whitelist_account!(controller);
321
322		#[extrinsic_call]
323		withdraw_unbonded(RawOrigin::Signed(controller.clone()), s);
324
325		let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created after")?;
326		let new_total: BalanceOf<T> = ledger.total;
327		assert!(original_total > new_total);
328
329		Ok(())
330	}
331
332	#[benchmark]
333	// Worst case scenario, everything is removed after the bonding duration
334	fn withdraw_unbonded_kill(
335		// Slashing Spans
336		s: Linear<0, MAX_SPANS>,
337	) -> Result<(), BenchmarkError> {
338		// clean up any existing state.
339		clear_validators_and_nominators::<T>();
340
341		let origin_weight = MinNominatorBond::<T>::get().max(asset::existential_deposit::<T>());
342
343		// setup a worst case list scenario. Note that we don't care about the setup of the
344		// destination position because we are doing a removal from the list but no insert.
345		let scenario = ListScenario::<T>::new(origin_weight, true)?;
346		let controller = scenario.origin_controller1.clone();
347		let stash = scenario.origin_stash1;
348		add_slashing_spans::<T>(&stash, s);
349		assert!(T::VoterList::contains(&stash));
350
351		let ed = asset::existential_deposit::<T>();
352		let mut ledger = Ledger::<T>::get(&controller).unwrap();
353		ledger.active = ed - One::one();
354		Ledger::<T>::insert(&controller, ledger);
355		CurrentEra::<T>::put(EraIndex::max_value());
356
357		whitelist_account!(controller);
358
359		#[extrinsic_call]
360		withdraw_unbonded(RawOrigin::Signed(controller.clone()), s);
361
362		assert!(!Ledger::<T>::contains_key(controller));
363		assert!(!T::VoterList::contains(&stash));
364
365		Ok(())
366	}
367
368	#[benchmark]
369	fn validate() -> Result<(), BenchmarkError> {
370		let (stash, controller) = create_stash_controller::<T>(
371			MaxNominationsOf::<T>::get() - 1,
372			100,
373			RewardDestination::Staked,
374		)?;
375		// because it is chilled.
376		assert!(!T::VoterList::contains(&stash));
377
378		let prefs = ValidatorPrefs::default();
379		whitelist_account!(controller);
380
381		#[extrinsic_call]
382		_(RawOrigin::Signed(controller), prefs);
383
384		assert!(Validators::<T>::contains_key(&stash));
385		assert!(T::VoterList::contains(&stash));
386
387		Ok(())
388	}
389
390	#[benchmark]
391	fn kick(
392		// scenario: we want to kick `k` nominators from nominating us (we are a validator).
393		// we'll assume that `k` is under 128 for the purposes of determining the slope.
394		// each nominator should have `T::MaxNominations::get()` validators nominated, and our
395		// validator should be somewhere in there.
396		k: Linear<1, 128>,
397	) -> Result<(), BenchmarkError> {
398		// these are the other validators; there are `T::MaxNominations::get() - 1` of them, so
399		// there are a total of `T::MaxNominations::get()` validators in the system.
400		let rest_of_validators =
401			create_validators_with_seed::<T>(MaxNominationsOf::<T>::get() - 1, 100, 415)?;
402
403		// this is the validator that will be kicking.
404		let (stash, controller) = create_stash_controller::<T>(
405			MaxNominationsOf::<T>::get() - 1,
406			100,
407			RewardDestination::Staked,
408		)?;
409		let stash_lookup = T::Lookup::unlookup(stash.clone());
410
411		// they start validating.
412		Staking::<T>::validate(RawOrigin::Signed(controller.clone()).into(), Default::default())?;
413
414		// we now create the nominators. there will be `k` of them; each will nominate all
415		// validators. we will then kick each of the `k` nominators from the main validator.
416		let mut nominator_stashes = Vec::with_capacity(k as usize);
417		for i in 0..k {
418			// create a nominator stash.
419			let (n_stash, n_controller) = create_stash_controller::<T>(
420				MaxNominationsOf::<T>::get() + i,
421				100,
422				RewardDestination::Staked,
423			)?;
424
425			// bake the nominations; we first clone them from the rest of the validators.
426			let mut nominations = rest_of_validators.clone();
427			// then insert "our" validator somewhere in there (we vary it) to avoid accidental
428			// optimisations/pessimisations.
429			nominations.insert(i as usize % (nominations.len() + 1), stash_lookup.clone());
430			// then we nominate.
431			Staking::<T>::nominate(RawOrigin::Signed(n_controller.clone()).into(), nominations)?;
432
433			nominator_stashes.push(n_stash);
434		}
435
436		// all nominators now should be nominating our validator...
437		for n in nominator_stashes.iter() {
438			assert!(Nominators::<T>::get(n).unwrap().targets.contains(&stash));
439		}
440
441		// we need the unlookuped version of the nominator stash for the kick.
442		let kicks = nominator_stashes
443			.iter()
444			.map(|n| T::Lookup::unlookup(n.clone()))
445			.collect::<Vec<_>>();
446
447		whitelist_account!(controller);
448
449		#[extrinsic_call]
450		_(RawOrigin::Signed(controller), kicks);
451
452		// all nominators now should *not* be nominating our validator...
453		for n in nominator_stashes.iter() {
454			assert!(!Nominators::<T>::get(n).unwrap().targets.contains(&stash));
455		}
456
457		Ok(())
458	}
459
460	#[benchmark]
461	// Worst case scenario, T::MaxNominations::get()
462	fn nominate(n: Linear<1, { MaxNominationsOf::<T>::get() }>) -> Result<(), BenchmarkError> {
463		// clean up any existing state.
464		clear_validators_and_nominators::<T>();
465
466		let origin_weight = MinNominatorBond::<T>::get().max(asset::existential_deposit::<T>());
467
468		// setup a worst case list scenario. Note we don't care about the destination position,
469		// because we are just doing an insert into the origin position.
470		ListScenario::<T>::new(origin_weight, true)?;
471		let (stash, controller) = create_stash_controller_with_balance::<T>(
472			SEED + MaxNominationsOf::<T>::get() + 1, /* make sure the account does not conflict
473			                                          * with others */
474			origin_weight,
475			RewardDestination::Staked,
476		)
477		.unwrap();
478
479		assert!(!Nominators::<T>::contains_key(&stash));
480		assert!(!T::VoterList::contains(&stash));
481
482		let validators = create_validators::<T>(n, 100).unwrap();
483		whitelist_account!(controller);
484
485		#[extrinsic_call]
486		_(RawOrigin::Signed(controller), validators);
487
488		assert!(Nominators::<T>::contains_key(&stash));
489		assert!(T::VoterList::contains(&stash));
490
491		Ok(())
492	}
493
494	#[benchmark]
495	fn chill() -> Result<(), BenchmarkError> {
496		// clean up any existing state.
497		clear_validators_and_nominators::<T>();
498
499		let origin_weight = MinNominatorBond::<T>::get().max(asset::existential_deposit::<T>());
500
501		// setup a worst case list scenario. Note that we don't care about the setup of the
502		// destination position because we are doing a removal from the list but no insert.
503		let scenario = ListScenario::<T>::new(origin_weight, true)?;
504		let controller = scenario.origin_controller1.clone();
505		let stash = scenario.origin_stash1;
506		assert!(T::VoterList::contains(&stash));
507
508		whitelist_account!(controller);
509
510		#[extrinsic_call]
511		_(RawOrigin::Signed(controller));
512
513		assert!(!T::VoterList::contains(&stash));
514
515		Ok(())
516	}
517
518	#[benchmark]
519	fn set_payee() -> Result<(), BenchmarkError> {
520		let (stash, controller) =
521			create_stash_controller::<T>(USER_SEED, 100, RewardDestination::Staked)?;
522		assert_eq!(Payee::<T>::get(&stash), Some(RewardDestination::Staked));
523		whitelist_account!(controller);
524
525		#[extrinsic_call]
526		_(RawOrigin::Signed(controller.clone()), RewardDestination::Account(controller.clone()));
527
528		assert_eq!(Payee::<T>::get(&stash), Some(RewardDestination::Account(controller)));
529
530		Ok(())
531	}
532
533	#[benchmark]
534	fn update_payee() -> Result<(), BenchmarkError> {
535		let (stash, controller) =
536			create_stash_controller::<T>(USER_SEED, 100, RewardDestination::Staked)?;
537		Payee::<T>::insert(&stash, {
538			#[allow(deprecated)]
539			RewardDestination::Controller
540		});
541		whitelist_account!(controller);
542
543		#[extrinsic_call]
544		_(RawOrigin::Signed(controller.clone()), controller.clone());
545
546		assert_eq!(Payee::<T>::get(&stash), Some(RewardDestination::Account(controller)));
547
548		Ok(())
549	}
550
551	#[benchmark]
552	fn set_controller() -> Result<(), BenchmarkError> {
553		let (stash, ctlr) =
554			create_unique_stash_controller::<T>(9000, 100, RewardDestination::Staked, false)?;
555		// ensure `ctlr` is the currently stored controller.
556		assert!(!Ledger::<T>::contains_key(&stash));
557		assert!(Ledger::<T>::contains_key(&ctlr));
558		assert_eq!(Bonded::<T>::get(&stash), Some(ctlr.clone()));
559
560		whitelist_account!(stash);
561
562		#[extrinsic_call]
563		_(RawOrigin::Signed(stash.clone()));
564
565		assert!(Ledger::<T>::contains_key(&stash));
566
567		Ok(())
568	}
569
570	#[benchmark]
571	fn set_validator_count() {
572		let validator_count = MaxValidators::<T>::get();
573
574		#[extrinsic_call]
575		_(RawOrigin::Root, validator_count);
576
577		assert_eq!(ValidatorCount::<T>::get(), validator_count);
578	}
579
580	#[benchmark]
581	fn force_no_eras() {
582		#[extrinsic_call]
583		_(RawOrigin::Root);
584
585		assert_eq!(ForceEra::<T>::get(), Forcing::ForceNone);
586	}
587
588	#[benchmark]
589	fn force_new_era() {
590		#[extrinsic_call]
591		_(RawOrigin::Root);
592
593		assert_eq!(ForceEra::<T>::get(), Forcing::ForceNew);
594	}
595
596	#[benchmark]
597	fn force_new_era_always() {
598		#[extrinsic_call]
599		_(RawOrigin::Root);
600
601		assert_eq!(ForceEra::<T>::get(), Forcing::ForceAlways);
602	}
603
604	#[benchmark]
605	// Worst case scenario, the list of invulnerables is very long.
606	fn set_invulnerables(v: Linear<0, { MaxValidators::<T>::get() }>) {
607		let mut invulnerables = Vec::new();
608		for i in 0..v {
609			invulnerables.push(account("invulnerable", i, SEED));
610		}
611
612		#[extrinsic_call]
613		_(RawOrigin::Root, invulnerables);
614
615		assert_eq!(Invulnerables::<T>::get().len(), v as usize);
616	}
617
618	#[benchmark]
619	fn deprecate_controller_batch(
620		// We pass a dynamic number of controllers to the benchmark, up to
621		// `MaxControllersInDeprecationBatch`.
622		u: Linear<0, { T::MaxControllersInDeprecationBatch::get() }>,
623	) -> Result<(), BenchmarkError> {
624		let mut controllers: Vec<_> = vec![];
625		let mut stashes: Vec<_> = vec![];
626		for i in 0..u as u32 {
627			let (stash, controller) =
628				create_unique_stash_controller::<T>(i, 100, RewardDestination::Staked, false)?;
629			controllers.push(controller);
630			stashes.push(stash);
631		}
632		let bounded_controllers: BoundedVec<_, T::MaxControllersInDeprecationBatch> =
633			BoundedVec::try_from(controllers.clone()).unwrap();
634
635		#[extrinsic_call]
636		_(RawOrigin::Root, bounded_controllers);
637
638		for i in 0..u as u32 {
639			let stash = &stashes[i as usize];
640			let controller = &controllers[i as usize];
641			// Ledger no longer keyed by controller.
642			assert_eq!(Ledger::<T>::get(controller), None);
643			// Bonded now maps to the stash.
644			assert_eq!(Bonded::<T>::get(stash), Some(stash.clone()));
645			// Ledger is now keyed by stash.
646			assert_eq!(Ledger::<T>::get(stash).unwrap().stash, *stash);
647		}
648
649		Ok(())
650	}
651
652	#[benchmark]
653	fn force_unstake(
654		// Slashing Spans
655		s: Linear<0, MAX_SPANS>,
656	) -> Result<(), BenchmarkError> {
657		// Clean up any existing state.
658		clear_validators_and_nominators::<T>();
659
660		let origin_weight = MinNominatorBond::<T>::get().max(asset::existential_deposit::<T>());
661
662		// setup a worst case list scenario. Note that we don't care about the setup of the
663		// destination position because we are doing a removal from the list but no insert.
664		let scenario = ListScenario::<T>::new(origin_weight, true)?;
665		let controller = scenario.origin_controller1.clone();
666		let stash = scenario.origin_stash1;
667		assert!(T::VoterList::contains(&stash));
668		add_slashing_spans::<T>(&stash, s);
669
670		#[extrinsic_call]
671		_(RawOrigin::Root, stash.clone(), s);
672
673		assert!(!Ledger::<T>::contains_key(&controller));
674		assert!(!T::VoterList::contains(&stash));
675
676		Ok(())
677	}
678
679	#[benchmark]
680	fn cancel_deferred_slash(s: Linear<1, MAX_SLASHES>) {
681		let mut unapplied_slashes = Vec::new();
682		let era = EraIndex::one();
683		let dummy = || T::AccountId::decode(&mut TrailingZeroInput::zeroes()).unwrap();
684		for _ in 0..MAX_SLASHES {
685			unapplied_slashes
686				.push(UnappliedSlash::<T::AccountId, BalanceOf<T>>::default_from(dummy()));
687		}
688		UnappliedSlashes::<T>::insert(era, &unapplied_slashes);
689
690		let slash_indices: Vec<u32> = (0..s).collect();
691
692		#[extrinsic_call]
693		_(RawOrigin::Root, era, slash_indices);
694
695		assert_eq!(UnappliedSlashes::<T>::get(&era).len(), (MAX_SLASHES - s) as usize);
696	}
697
698	#[benchmark]
699	fn payout_stakers_alive_staked(
700		n: Linear<0, { T::MaxExposurePageSize::get() as u32 }>,
701	) -> Result<(), BenchmarkError> {
702		let (validator, nominators) = create_validator_with_nominators::<T>(
703			n,
704			T::MaxExposurePageSize::get() as u32,
705			false,
706			true,
707			RewardDestination::Staked,
708		)?;
709
710		let current_era = CurrentEra::<T>::get().unwrap();
711		// set the commission for this particular era as well.
712		<ErasValidatorPrefs<T>>::insert(
713			current_era,
714			validator.clone(),
715			Validators::<T>::get(&validator),
716		);
717
718		let caller = whitelisted_caller();
719		let balance_before = asset::stakeable_balance::<T>(&validator);
720		let mut nominator_balances_before = Vec::new();
721		for (stash, _) in &nominators {
722			let balance = asset::stakeable_balance::<T>(stash);
723			nominator_balances_before.push(balance);
724		}
725
726		#[extrinsic_call]
727		payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era);
728
729		let balance_after = asset::stakeable_balance::<T>(&validator);
730		ensure!(
731			balance_before < balance_after,
732			"Balance of validator stash should have increased after payout.",
733		);
734		for ((stash, _), balance_before) in nominators.iter().zip(nominator_balances_before.iter())
735		{
736			let balance_after = asset::stakeable_balance::<T>(stash);
737			ensure!(
738				balance_before < &balance_after,
739				"Balance of nominator stash should have increased after payout.",
740			);
741		}
742
743		Ok(())
744	}
745
746	#[benchmark]
747	fn rebond(l: Linear<1, { T::MaxUnlockingChunks::get() as u32 }>) -> Result<(), BenchmarkError> {
748		// clean up any existing state.
749		clear_validators_and_nominators::<T>();
750
751		let origin_weight = MinNominatorBond::<T>::get()
752			.max(asset::existential_deposit::<T>())
753			// we use 100 to play friendly with the list threshold values in the mock
754			.max(100u32.into());
755
756		// setup a worst case list scenario.
757		let scenario = ListScenario::<T>::new(origin_weight, true)?;
758		let dest_weight = scenario.dest_weight;
759
760		// rebond an amount that will give the user dest_weight
761		let rebond_amount = dest_weight - origin_weight;
762
763		// spread that amount to rebond across `l` unlocking chunks,
764		let value = rebond_amount / l.into();
765		// if `value` is zero, we need a greater delta between dest <=> origin weight
766		assert_ne!(value, Zero::zero());
767		// so the sum of unlocking chunks puts voter into the dest bag.
768		assert!(value * l.into() + origin_weight > origin_weight);
769		assert!(value * l.into() + origin_weight <= dest_weight);
770		let unlock_chunk = UnlockChunk::<BalanceOf<T>> { value, era: EraIndex::zero() };
771
772		let controller = scenario.origin_controller1;
773		let mut staking_ledger = Ledger::<T>::get(controller.clone()).unwrap();
774
775		for _ in 0..l {
776			staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap()
777		}
778		Ledger::<T>::insert(controller.clone(), staking_ledger.clone());
779		let original_bonded: BalanceOf<T> = staking_ledger.active;
780
781		whitelist_account!(controller);
782
783		#[extrinsic_call]
784		_(RawOrigin::Signed(controller.clone()), rebond_amount);
785
786		let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created after")?;
787		let new_bonded: BalanceOf<T> = ledger.active;
788		assert!(original_bonded < new_bonded);
789
790		Ok(())
791	}
792
793	#[benchmark]
794	fn reap_stash(s: Linear<1, MAX_SPANS>) -> Result<(), BenchmarkError> {
795		// clean up any existing state.
796		clear_validators_and_nominators::<T>();
797
798		let origin_weight = MinNominatorBond::<T>::get().max(asset::existential_deposit::<T>());
799
800		// setup a worst case list scenario. Note that we don't care about the setup of the
801		// destination position because we are doing a removal from the list but no insert.
802		let scenario = ListScenario::<T>::new(origin_weight, true)?;
803		let controller = scenario.origin_controller1.clone();
804		let stash = scenario.origin_stash1;
805
806		add_slashing_spans::<T>(&stash, s);
807		let l =
808			StakingLedger::<T>::new(stash.clone(), asset::existential_deposit::<T>() - One::one());
809		Ledger::<T>::insert(&controller, l);
810
811		assert!(Bonded::<T>::contains_key(&stash));
812		assert!(T::VoterList::contains(&stash));
813
814		whitelist_account!(controller);
815
816		#[extrinsic_call]
817		_(RawOrigin::Signed(controller), stash.clone(), s);
818
819		assert!(!Bonded::<T>::contains_key(&stash));
820		assert!(!T::VoterList::contains(&stash));
821
822		Ok(())
823	}
824
825	#[benchmark]
826	fn new_era(v: Linear<1, 10>, n: Linear<0, 100>) -> Result<(), BenchmarkError> {
827		create_validators_with_nominators_for_era::<T>(
828			v,
829			n,
830			MaxNominationsOf::<T>::get() as usize,
831			false,
832			None,
833		)?;
834		let session_index = SessionIndex::one();
835
836		let validators;
837		#[block]
838		{
839			validators =
840				Staking::<T>::try_trigger_new_era(session_index, true).ok_or("`new_era` failed")?;
841		}
842
843		assert!(validators.len() == v as usize);
844
845		Ok(())
846	}
847
848	#[benchmark(extra)]
849	fn payout_all(v: Linear<1, 10>, n: Linear<0, 100>) -> Result<(), BenchmarkError> {
850		create_validators_with_nominators_for_era::<T>(
851			v,
852			n,
853			MaxNominationsOf::<T>::get() as usize,
854			false,
855			None,
856		)?;
857		// Start a new Era
858		let new_validators = Staking::<T>::try_trigger_new_era(SessionIndex::one(), true).unwrap();
859		assert!(new_validators.len() == v as usize);
860
861		let current_era = CurrentEra::<T>::get().unwrap();
862		let mut points_total = 0;
863		let mut points_individual = Vec::new();
864		let mut payout_calls_arg = Vec::new();
865
866		for validator in new_validators.iter() {
867			points_total += 10;
868			points_individual.push((validator.clone(), 10));
869			payout_calls_arg.push((validator.clone(), current_era));
870		}
871
872		// Give Era Points
873		let reward = EraRewardPoints::<T::AccountId> {
874			total: points_total,
875			individual: points_individual.into_iter().collect(),
876		};
877
878		ErasRewardPoints::<T>::insert(current_era, reward);
879
880		// Create reward pool
881		let total_payout = asset::existential_deposit::<T>() * 1000u32.into();
882		<ErasValidatorReward<T>>::insert(current_era, total_payout);
883
884		let caller: T::AccountId = whitelisted_caller();
885		let origin = RawOrigin::Signed(caller);
886		let calls: Vec<_> = payout_calls_arg
887			.iter()
888			.map(|arg| {
889				Call::<T>::payout_stakers_by_page {
890					validator_stash: arg.0.clone(),
891					era: arg.1,
892					page: 0,
893				}
894				.encode()
895			})
896			.collect();
897
898		#[block]
899		{
900			for call in calls {
901				<Call<T> as Decode>::decode(&mut &*call)
902					.expect("call is encoded above, encoding must be correct")
903					.dispatch_bypass_filter(origin.clone().into())?;
904			}
905		}
906
907		Ok(())
908	}
909
910	#[benchmark(extra)]
911	fn do_slash(
912		l: Linear<1, { T::MaxUnlockingChunks::get() as u32 }>,
913	) -> Result<(), BenchmarkError> {
914		let (stash, controller) = create_stash_controller::<T>(0, 100, RewardDestination::Staked)?;
915		let mut staking_ledger = Ledger::<T>::get(controller.clone()).unwrap();
916		let unlock_chunk =
917			UnlockChunk::<BalanceOf<T>> { value: 1u32.into(), era: EraIndex::zero() };
918		for _ in 0..l {
919			staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap();
920		}
921		Ledger::<T>::insert(controller, staking_ledger);
922		let slash_amount = asset::existential_deposit::<T>() * 10u32.into();
923		let balance_before = asset::stakeable_balance::<T>(&stash);
924
925		#[block]
926		{
927			crate::slashing::do_slash::<T>(
928				&stash,
929				slash_amount,
930				&mut BalanceOf::<T>::zero(),
931				&mut NegativeImbalanceOf::<T>::zero(),
932				EraIndex::zero(),
933			);
934		}
935
936		let balance_after = asset::stakeable_balance::<T>(&stash);
937		assert!(balance_before > balance_after);
938
939		Ok(())
940	}
941
942	#[benchmark]
943	fn get_npos_voters(
944		// number of validator intention. we will iterate all of them.
945		v: Linear<{ MaxValidators::<T>::get() / 2 }, { MaxValidators::<T>::get() }>,
946
947		// number of nominator intention. we will iterate all of them.
948		n: Linear<{ MaxNominators::<T>::get() / 2 }, { MaxNominators::<T>::get() }>,
949	) -> Result<(), BenchmarkError> {
950		create_validators_with_nominators_for_era::<T>(
951			v,
952			n,
953			MaxNominationsOf::<T>::get() as usize,
954			false,
955			None,
956		)?;
957
958		assert_eq!(Validators::<T>::count(), v);
959		assert_eq!(Nominators::<T>::count(), n);
960
961		let num_voters = (v + n) as usize;
962
963		// default bounds are unbounded.
964		let voters;
965		#[block]
966		{
967			voters = <Staking<T>>::get_npos_voters(DataProviderBounds::default());
968		}
969
970		assert_eq!(voters.len(), num_voters);
971
972		Ok(())
973	}
974
975	#[benchmark]
976	fn get_npos_targets(
977		// number of validator intention.
978		v: Linear<{ MaxValidators::<T>::get() / 2 }, { MaxValidators::<T>::get() }>,
979	) -> Result<(), BenchmarkError> {
980		// number of nominator intention.
981		let n = MaxNominators::<T>::get();
982		create_validators_with_nominators_for_era::<T>(
983			v,
984			n,
985			MaxNominationsOf::<T>::get() as usize,
986			false,
987			None,
988		)?;
989
990		let targets;
991
992		#[block]
993		{
994			// default bounds are unbounded.
995			targets = <Staking<T>>::get_npos_targets(DataProviderBounds::default());
996		}
997
998		assert_eq!(targets.len() as u32, v);
999
1000		Ok(())
1001	}
1002
1003	#[benchmark]
1004	fn set_staking_configs_all_set() {
1005		#[extrinsic_call]
1006		set_staking_configs(
1007			RawOrigin::Root,
1008			ConfigOp::Set(BalanceOf::<T>::max_value()),
1009			ConfigOp::Set(BalanceOf::<T>::max_value()),
1010			ConfigOp::Set(u32::MAX),
1011			ConfigOp::Set(u32::MAX),
1012			ConfigOp::Set(Percent::max_value()),
1013			ConfigOp::Set(Perbill::max_value()),
1014			ConfigOp::Set(Percent::max_value()),
1015		);
1016
1017		assert_eq!(MinNominatorBond::<T>::get(), BalanceOf::<T>::max_value());
1018		assert_eq!(MinValidatorBond::<T>::get(), BalanceOf::<T>::max_value());
1019		assert_eq!(MaxNominatorsCount::<T>::get(), Some(u32::MAX));
1020		assert_eq!(MaxValidatorsCount::<T>::get(), Some(u32::MAX));
1021		assert_eq!(ChillThreshold::<T>::get(), Some(Percent::from_percent(100)));
1022		assert_eq!(MinCommission::<T>::get(), Perbill::from_percent(100));
1023		assert_eq!(MaxStakedRewards::<T>::get(), Some(Percent::from_percent(100)));
1024	}
1025
1026	#[benchmark]
1027	fn set_staking_configs_all_remove() {
1028		#[extrinsic_call]
1029		set_staking_configs(
1030			RawOrigin::Root,
1031			ConfigOp::Remove,
1032			ConfigOp::Remove,
1033			ConfigOp::Remove,
1034			ConfigOp::Remove,
1035			ConfigOp::Remove,
1036			ConfigOp::Remove,
1037			ConfigOp::Remove,
1038		);
1039
1040		assert!(!MinNominatorBond::<T>::exists());
1041		assert!(!MinValidatorBond::<T>::exists());
1042		assert!(!MaxNominatorsCount::<T>::exists());
1043		assert!(!MaxValidatorsCount::<T>::exists());
1044		assert!(!ChillThreshold::<T>::exists());
1045		assert!(!MinCommission::<T>::exists());
1046		assert!(!MaxStakedRewards::<T>::exists());
1047	}
1048
1049	#[benchmark]
1050	fn chill_other() -> Result<(), BenchmarkError> {
1051		// clean up any existing state.
1052		clear_validators_and_nominators::<T>();
1053
1054		let origin_weight = MinNominatorBond::<T>::get().max(asset::existential_deposit::<T>());
1055
1056		// setup a worst case list scenario. Note that we don't care about the setup of the
1057		// destination position because we are doing a removal from the list but no insert.
1058		let scenario = ListScenario::<T>::new(origin_weight, true)?;
1059		let stash = scenario.origin_stash1;
1060		assert!(T::VoterList::contains(&stash));
1061
1062		Staking::<T>::set_staking_configs(
1063			RawOrigin::Root.into(),
1064			ConfigOp::Set(BalanceOf::<T>::max_value()),
1065			ConfigOp::Set(BalanceOf::<T>::max_value()),
1066			ConfigOp::Set(0),
1067			ConfigOp::Set(0),
1068			ConfigOp::Set(Percent::from_percent(0)),
1069			ConfigOp::Set(Zero::zero()),
1070			ConfigOp::Noop,
1071		)?;
1072
1073		let caller = whitelisted_caller();
1074
1075		#[extrinsic_call]
1076		_(RawOrigin::Signed(caller), stash.clone());
1077
1078		assert!(!T::VoterList::contains(&stash));
1079
1080		Ok(())
1081	}
1082
1083	#[benchmark]
1084	fn force_apply_min_commission() -> Result<(), BenchmarkError> {
1085		// Clean up any existing state
1086		clear_validators_and_nominators::<T>();
1087
1088		// Create a validator with a commission of 50%
1089		let (stash, controller) = create_stash_controller::<T>(1, 1, RewardDestination::Staked)?;
1090		let validator_prefs =
1091			ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
1092		Staking::<T>::validate(RawOrigin::Signed(controller).into(), validator_prefs)?;
1093
1094		// Sanity check
1095		assert_eq!(
1096			Validators::<T>::get(&stash),
1097			ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }
1098		);
1099
1100		// Set the min commission to 75%
1101		MinCommission::<T>::set(Perbill::from_percent(75));
1102		let caller = whitelisted_caller();
1103
1104		#[extrinsic_call]
1105		_(RawOrigin::Signed(caller), stash.clone());
1106
1107		// The validators commission has been bumped to 75%
1108		assert_eq!(
1109			Validators::<T>::get(&stash),
1110			ValidatorPrefs { commission: Perbill::from_percent(75), ..Default::default() }
1111		);
1112
1113		Ok(())
1114	}
1115
1116	#[benchmark]
1117	fn set_min_commission() {
1118		let min_commission = Perbill::max_value();
1119
1120		#[extrinsic_call]
1121		_(RawOrigin::Root, min_commission);
1122
1123		assert_eq!(MinCommission::<T>::get(), Perbill::from_percent(100));
1124	}
1125
1126	#[benchmark]
1127	fn restore_ledger() -> Result<(), BenchmarkError> {
1128		let (stash, controller) = create_stash_controller::<T>(0, 100, RewardDestination::Staked)?;
1129		// corrupt ledger.
1130		Ledger::<T>::remove(controller);
1131
1132		#[extrinsic_call]
1133		_(RawOrigin::Root, stash.clone(), None, None, None);
1134
1135		assert_eq!(Staking::<T>::inspect_bond_state(&stash), Ok(LedgerIntegrityState::Ok));
1136
1137		Ok(())
1138	}
1139
1140	#[benchmark]
1141	fn migrate_currency() -> Result<(), BenchmarkError> {
1142		let (stash, _ctrl) =
1143			create_stash_controller::<T>(USER_SEED, 100, RewardDestination::Staked)?;
1144		let stake = asset::staked::<T>(&stash);
1145		migrate_to_old_currency::<T>(stash.clone());
1146		// no holds
1147		assert!(asset::staked::<T>(&stash).is_zero());
1148		whitelist_account!(stash);
1149
1150		#[extrinsic_call]
1151		_(RawOrigin::Signed(stash.clone()), stash.clone());
1152
1153		assert_eq!(asset::staked::<T>(&stash), stake);
1154		Ok(())
1155	}
1156
1157	#[benchmark]
1158	fn manual_slash() -> Result<(), BenchmarkError> {
1159		// Create a validator with nominators
1160		// This will add exposure for our validator in the current era.
1161		let (validator_stash, _nominators) = create_validator_with_nominators::<T>(
1162			T::MaxExposurePageSize::get() as u32,
1163			T::MaxExposurePageSize::get() as u32,
1164			false,
1165			true,
1166			RewardDestination::Staked,
1167		)?;
1168
1169		let era = CurrentEra::<T>::get().unwrap();
1170		ActiveEra::<T>::put(ActiveEraInfo { index: era, start: None });
1171		let slash_fraction = Perbill::from_percent(10);
1172
1173		#[extrinsic_call]
1174		_(RawOrigin::Root, validator_stash.clone(), era, slash_fraction);
1175
1176		assert!(ValidatorSlashInEra::<T>::get(era, &validator_stash).is_some());
1177
1178		Ok(())
1179	}
1180
1181	impl_benchmark_test_suite!(
1182		Staking,
1183		crate::mock::ExtBuilder::default().has_stakers(true),
1184		crate::mock::Test,
1185		exec_name = build_and_execute
1186	);
1187}
1188
1189#[cfg(test)]
1190mod tests {
1191	use super::*;
1192	use crate::mock::{ExtBuilder, RuntimeOrigin, Staking, Test};
1193	use frame_support::assert_ok;
1194
1195	#[test]
1196	fn create_validators_with_nominators_for_era_works() {
1197		ExtBuilder::default().build_and_execute(|| {
1198			let v = 10;
1199			let n = 100;
1200
1201			create_validators_with_nominators_for_era::<Test>(
1202				v,
1203				n,
1204				MaxNominationsOf::<Test>::get() as usize,
1205				false,
1206				None,
1207			)
1208			.unwrap();
1209
1210			let count_validators = Validators::<Test>::iter().count();
1211			let count_nominators = Nominators::<Test>::iter().count();
1212
1213			assert_eq!(count_validators, Validators::<Test>::count() as usize);
1214			assert_eq!(count_nominators, Nominators::<Test>::count() as usize);
1215
1216			assert_eq!(count_validators, v as usize);
1217			assert_eq!(count_nominators, n as usize);
1218		});
1219	}
1220
1221	#[test]
1222	fn create_validator_with_nominators_works() {
1223		ExtBuilder::default().build_and_execute(|| {
1224			let n = 10;
1225
1226			let (validator_stash, nominators) = create_validator_with_nominators::<Test>(
1227				n,
1228				<<Test as Config>::MaxExposurePageSize as Get<_>>::get(),
1229				false,
1230				false,
1231				RewardDestination::Staked,
1232			)
1233			.unwrap();
1234
1235			assert_eq!(nominators.len() as u32, n);
1236
1237			let current_era = CurrentEra::<Test>::get().unwrap();
1238
1239			let original_stakeable_balance = asset::stakeable_balance::<Test>(&validator_stash);
1240			assert_ok!(Staking::payout_stakers_by_page(
1241				RuntimeOrigin::signed(1337),
1242				validator_stash,
1243				current_era,
1244				0
1245			));
1246			let new_stakeable_balance = asset::stakeable_balance::<Test>(&validator_stash);
1247
1248			// reward increases stakeable balance
1249			assert!(original_stakeable_balance < new_stakeable_balance);
1250		});
1251	}
1252
1253	#[test]
1254	fn add_slashing_spans_works() {
1255		ExtBuilder::default().build_and_execute(|| {
1256			let n = 10;
1257
1258			let (validator_stash, _nominators) = create_validator_with_nominators::<Test>(
1259				n,
1260				<<Test as Config>::MaxExposurePageSize as Get<_>>::get(),
1261				false,
1262				false,
1263				RewardDestination::Staked,
1264			)
1265			.unwrap();
1266
1267			// Add 20 slashing spans
1268			let num_of_slashing_spans = 20;
1269			add_slashing_spans::<Test>(&validator_stash, num_of_slashing_spans);
1270
1271			let slashing_spans = SlashingSpans::<Test>::get(&validator_stash).unwrap();
1272			assert_eq!(slashing_spans.iter().count(), num_of_slashing_spans as usize);
1273			for i in 0..num_of_slashing_spans {
1274				assert!(SpanSlash::<Test>::contains_key((&validator_stash, i)));
1275			}
1276
1277			// Test everything is cleaned up
1278			assert_ok!(Staking::kill_stash(&validator_stash, num_of_slashing_spans));
1279			assert!(SlashingSpans::<Test>::get(&validator_stash).is_none());
1280			for i in 0..num_of_slashing_spans {
1281				assert!(!SpanSlash::<Test>::contains_key((&validator_stash, i)));
1282			}
1283		});
1284	}
1285}