referrerpolicy=no-referrer-when-downgrade

pallet_staking/
testing_utils.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//! Testing utils for staking. Provides some common functions to setup staking state, such as
19//! bonding validators, nominators, and generating different types of solutions.
20
21use crate::{Pallet as Staking, *};
22use frame_benchmarking::account;
23use frame_system::RawOrigin;
24use rand_chacha::{
25	rand_core::{RngCore, SeedableRng},
26	ChaChaRng,
27};
28use sp_io::hashing::blake2_256;
29
30use frame_election_provider_support::SortedListProvider;
31use frame_support::pallet_prelude::*;
32use sp_runtime::{traits::StaticLookup, Perbill};
33
34const SEED: u32 = 0;
35
36/// This function removes all validators and nominators from storage.
37pub fn clear_validators_and_nominators<T: Config>() {
38	#[allow(deprecated)]
39	Validators::<T>::remove_all();
40
41	// whenever we touch nominators counter we should update `T::VoterList` as well.
42	#[allow(deprecated)]
43	Nominators::<T>::remove_all();
44
45	// NOTE: safe to call outside block production
46	T::VoterList::unsafe_clear();
47}
48
49/// Grab a funded user.
50pub fn create_funded_user<T: Config>(
51	string: &'static str,
52	n: u32,
53	balance_factor: u32,
54) -> T::AccountId {
55	let user = account(string, n, SEED);
56	let balance = asset::existential_deposit::<T>() * balance_factor.into();
57	let _ = asset::set_stakeable_balance::<T>(&user, balance);
58	user
59}
60
61/// Grab a funded user with max Balance.
62pub fn create_funded_user_with_balance<T: Config>(
63	string: &'static str,
64	n: u32,
65	balance: BalanceOf<T>,
66) -> T::AccountId {
67	let user = account(string, n, SEED);
68	let _ = asset::set_stakeable_balance::<T>(&user, balance);
69	user
70}
71
72/// Create a stash and controller pair.
73pub fn create_stash_controller<T: Config>(
74	n: u32,
75	balance_factor: u32,
76	destination: RewardDestination<T::AccountId>,
77) -> Result<(T::AccountId, T::AccountId), &'static str> {
78	let staker = create_funded_user::<T>("stash", n, balance_factor);
79	let amount =
80		asset::existential_deposit::<T>().max(1u64.into()) * (balance_factor / 10).max(1).into();
81	Staking::<T>::bond(RawOrigin::Signed(staker.clone()).into(), amount, destination)?;
82	Ok((staker.clone(), staker))
83}
84
85/// Create a unique stash and controller pair.
86pub fn create_unique_stash_controller<T: Config>(
87	n: u32,
88	balance_factor: u32,
89	destination: RewardDestination<T::AccountId>,
90	dead_controller: bool,
91) -> Result<(T::AccountId, T::AccountId), &'static str> {
92	let stash = create_funded_user::<T>("stash", n, balance_factor);
93
94	let controller = if dead_controller {
95		create_funded_user::<T>("controller", n, 0)
96	} else {
97		create_funded_user::<T>("controller", n, balance_factor)
98	};
99	let amount = asset::existential_deposit::<T>() * (balance_factor / 10).max(1).into();
100	Staking::<T>::bond(RawOrigin::Signed(stash.clone()).into(), amount, destination)?;
101
102	// update ledger to be a *different* controller to stash
103	if let Some(l) = Ledger::<T>::take(&stash) {
104		<Ledger<T>>::insert(&controller, l);
105	}
106	// update bonded account to be unique controller
107	<Bonded<T>>::insert(&stash, &controller);
108
109	Ok((stash, controller))
110}
111
112/// Create a stash and controller pair with fixed balance.
113pub fn create_stash_controller_with_balance<T: Config>(
114	n: u32,
115	balance: crate::BalanceOf<T>,
116	destination: RewardDestination<T::AccountId>,
117) -> Result<(T::AccountId, T::AccountId), &'static str> {
118	let staker = create_funded_user_with_balance::<T>("stash", n, balance);
119	Staking::<T>::bond(RawOrigin::Signed(staker.clone()).into(), balance, destination)?;
120	Ok((staker.clone(), staker))
121}
122
123/// Create a stash and controller pair, where payouts go to a dead payee account. This is used to
124/// test worst case payout scenarios.
125pub fn create_stash_and_dead_payee<T: Config>(
126	n: u32,
127	balance_factor: u32,
128) -> Result<(T::AccountId, T::AccountId), &'static str> {
129	let staker = create_funded_user::<T>("stash", n, 0);
130	// payee has no funds
131	let payee = create_funded_user::<T>("payee", n, 0);
132	let amount = asset::existential_deposit::<T>() * (balance_factor / 10).max(1).into();
133	Staking::<T>::bond(
134		RawOrigin::Signed(staker.clone()).into(),
135		amount,
136		RewardDestination::Account(payee),
137	)?;
138	Ok((staker.clone(), staker))
139}
140
141/// create `max` validators.
142pub fn create_validators<T: Config>(
143	max: u32,
144	balance_factor: u32,
145) -> Result<Vec<AccountIdLookupOf<T>>, &'static str> {
146	create_validators_with_seed::<T>(max, balance_factor, 0)
147}
148
149/// create `max` validators, with a seed to help unintentional prevent account collisions.
150pub fn create_validators_with_seed<T: Config>(
151	max: u32,
152	balance_factor: u32,
153	seed: u32,
154) -> Result<Vec<AccountIdLookupOf<T>>, &'static str> {
155	let mut validators: Vec<AccountIdLookupOf<T>> = Vec::with_capacity(max as usize);
156	for i in 0..max {
157		let (stash, controller) =
158			create_stash_controller::<T>(i + seed, balance_factor, RewardDestination::Staked)?;
159		let validator_prefs =
160			ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
161		Staking::<T>::validate(RawOrigin::Signed(controller).into(), validator_prefs)?;
162		let stash_lookup = T::Lookup::unlookup(stash);
163		validators.push(stash_lookup);
164	}
165	Ok(validators)
166}
167
168/// This function generates validators and nominators who are randomly nominating
169/// `edge_per_nominator` random validators (until `to_nominate` if provided).
170///
171/// NOTE: This function will remove any existing validators or nominators to ensure
172/// we are working with a clean state.
173///
174/// Parameters:
175/// - `validators`: number of bonded validators
176/// - `nominators`: number of bonded nominators.
177/// - `edge_per_nominator`: number of edge (vote) per nominator.
178/// - `randomize_stake`: whether to randomize the stakes.
179/// - `to_nominate`: if `Some(n)`, only the first `n` bonded validator are voted upon. Else, all of
180///   them are considered and `edge_per_nominator` random validators are voted for.
181///
182/// Return the validators chosen to be nominated.
183pub fn create_validators_with_nominators_for_era<T: Config>(
184	validators: u32,
185	nominators: u32,
186	edge_per_nominator: usize,
187	randomize_stake: bool,
188	to_nominate: Option<u32>,
189) -> Result<Vec<AccountIdLookupOf<T>>, &'static str> {
190	clear_validators_and_nominators::<T>();
191
192	let mut validators_stash: Vec<AccountIdLookupOf<T>> = Vec::with_capacity(validators as usize);
193	let mut rng = ChaChaRng::from_seed(SEED.using_encoded(blake2_256));
194
195	// Create validators
196	for i in 0..validators {
197		let balance_factor = if randomize_stake { rng.next_u32() % 255 + 10 } else { 100u32 };
198		let (v_stash, v_controller) =
199			create_stash_controller::<T>(i, balance_factor, RewardDestination::Staked)?;
200		let validator_prefs =
201			ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
202		Staking::<T>::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?;
203		let stash_lookup = T::Lookup::unlookup(v_stash.clone());
204		validators_stash.push(stash_lookup.clone());
205	}
206
207	let to_nominate = to_nominate.unwrap_or(validators_stash.len() as u32) as usize;
208	let validator_chosen = validators_stash[0..to_nominate].to_vec();
209
210	// Create nominators
211	for j in 0..nominators {
212		let balance_factor = if randomize_stake { rng.next_u32() % 255 + 10 } else { 100u32 };
213		let (_n_stash, n_controller) =
214			create_stash_controller::<T>(u32::MAX - j, balance_factor, RewardDestination::Staked)?;
215
216		// Have them randomly validate
217		let mut available_validators = validator_chosen.clone();
218		let mut selected_validators: Vec<AccountIdLookupOf<T>> =
219			Vec::with_capacity(edge_per_nominator);
220
221		for _ in 0..validators.min(edge_per_nominator as u32) {
222			let selected = rng.next_u32() as usize % available_validators.len();
223			let validator = available_validators.remove(selected);
224			selected_validators.push(validator);
225		}
226		Staking::<T>::nominate(
227			RawOrigin::Signed(n_controller.clone()).into(),
228			selected_validators,
229		)?;
230	}
231
232	ValidatorCount::<T>::put(validators);
233
234	Ok(validator_chosen)
235}
236
237/// get the current era.
238pub fn current_era<T: Config>() -> EraIndex {
239	CurrentEra::<T>::get().unwrap_or(0)
240}
241
242pub fn migrate_to_old_currency<T: Config>(who: T::AccountId) {
243	use frame_support::traits::LockableCurrency;
244	let staked = asset::staked::<T>(&who);
245
246	// apply locks (this also adds a consumer).
247	T::OldCurrency::set_lock(
248		STAKING_ID,
249		&who,
250		staked,
251		frame_support::traits::WithdrawReasons::all(),
252	);
253	// remove holds.
254	asset::kill_stake::<T>(&who).expect("remove hold failed");
255
256	// replicate old behaviour of explicit increment of consumer.
257	frame_system::Pallet::<T>::inc_consumers(&who).expect("increment consumer failed");
258}