referrerpolicy=no-referrer-when-downgrade

pallet_fast_unstake/
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//! Benchmarking for pallet-fast-unstake.
19
20#![cfg(feature = "runtime-benchmarks")]
21
22use crate::{types::*, *};
23use alloc::vec::Vec;
24use frame_benchmarking::v2::*;
25use frame_support::{
26	assert_ok,
27	traits::{Currency, EnsureOrigin, Get, Hooks},
28};
29use frame_system::RawOrigin;
30use sp_runtime::traits::Zero;
31use sp_staking::{EraIndex, StakingInterface};
32
33const USER_SEED: u32 = 0;
34
35type CurrencyOf<T> = <T as Config>::Currency;
36
37fn create_unexposed_batch<T: Config>(batch_size: u32) -> Vec<T::AccountId> {
38	(0..batch_size)
39		.map(|i| {
40			let account =
41				frame_benchmarking::account::<T::AccountId>("unexposed_nominator", i, USER_SEED);
42			fund_and_bond_account::<T>(&account);
43			account
44		})
45		.collect()
46}
47
48fn fund_and_bond_account<T: Config>(account: &T::AccountId) {
49	let stake = CurrencyOf::<T>::minimum_balance() * 100u32.into();
50	CurrencyOf::<T>::make_free_balance_be(&account, stake * 100u32.into());
51
52	// bond without nominating - fast-unstake works with non-nominating bonded accounts.
53	// Note that if we want to nominate a validator, we must first create a valid one; otherwise,
54	// `nominate()` will fail with a BadTarget error. However, fast-unstaking is intended for
55	// accounts that are bonded but not actively nominating, making this an unnecessary complication
56	// for our needs.
57	assert_ok!(T::Staking::bond(account, stake, account));
58}
59
60pub(crate) fn fast_unstake_events<T: Config>() -> Vec<crate::Event<T>> {
61	frame_system::Pallet::<T>::events()
62		.into_iter()
63		.map(|r| r.event)
64		.filter_map(|e| <T as Config>::RuntimeEvent::from(e).try_into().ok())
65		.collect::<Vec<_>>()
66}
67
68fn setup_staking<T: Config>(v: u32, until: EraIndex) {
69	let ed = CurrencyOf::<T>::minimum_balance();
70
71	log!(debug, "registering {} validators and {} eras.", v, until);
72
73	// our validators don't actually need to registered in staking -- just generate `v` random
74	// accounts.
75	let validators = (0..v)
76		.map(|x| frame_benchmarking::account::<T::AccountId>("validator", x, USER_SEED))
77		.collect::<Vec<_>>();
78
79	for era in 0..=until {
80		let others = (0..T::Staking::max_exposure_page_size())
81			.map(|s| {
82				let who = frame_benchmarking::account::<T::AccountId>("nominator", era, s.into());
83				let value = ed;
84				(who, value)
85			})
86			.collect::<Vec<_>>();
87		validators.iter().for_each(|v| {
88			T::Staking::add_era_stakers(&era, &v, others.clone());
89		});
90	}
91}
92
93fn on_idle_full_block<T: Config>() {
94	let remaining_weight = <T as frame_system::Config>::BlockWeights::get().max_block;
95	Pallet::<T>::on_idle(Zero::zero(), remaining_weight);
96}
97
98#[benchmarks]
99mod benchmarks {
100	use super::*;
101	// on_idle, we don't check anyone, but fully unbond them.
102	#[benchmark]
103	fn on_idle_unstake(b: Linear<1, { T::BatchSize::get() }>) {
104		ErasToCheckPerBlock::<T>::put(1);
105		for who in create_unexposed_batch::<T>(b).into_iter() {
106			assert_ok!(Pallet::<T>::register_fast_unstake(RawOrigin::Signed(who.clone()).into(),));
107		}
108
109		// Run on_idle once. This will check era 0.
110		assert_eq!(Head::<T>::get(), None);
111		on_idle_full_block::<T>();
112
113		assert!(matches!(
114			Head::<T>::get(),
115			Some(UnstakeRequest {
116				checked,
117				stashes,
118				..
119			}) if checked.len() == 1 && stashes.len() as u32 == b
120		));
121
122		#[block]
123		{
124			on_idle_full_block::<T>();
125		}
126
127		assert_eq!(fast_unstake_events::<T>().last(), Some(&Event::BatchFinished { size: b }));
128	}
129
130	#[benchmark]
131	fn on_idle_check(v: Linear<1, 256>, b: Linear<1, { T::BatchSize::get() }>) {
132		// on_idle: When we check some number of eras and the queue is already set.
133
134		let u = T::MaxErasToCheckPerBlock::get().min(T::Staking::bonding_duration());
135
136		ErasToCheckPerBlock::<T>::put(u);
137		T::Staking::set_current_era(u);
138
139		// setup staking with v validators and u eras of data (0..=u+1)
140		setup_staking::<T>(v, u);
141
142		let stashes = create_unexposed_batch::<T>(b)
143			.into_iter()
144			.map(|s| {
145				assert_ok!(
146					Pallet::<T>::register_fast_unstake(RawOrigin::Signed(s.clone()).into(),)
147				);
148				(s, T::Deposit::get())
149			})
150			.collect::<Vec<_>>();
151
152		// no one is queued thus far.
153		assert_eq!(Head::<T>::get(), None);
154
155		Head::<T>::put(UnstakeRequest {
156			stashes: stashes.clone().try_into().unwrap(),
157			checked: Default::default(),
158		});
159
160		#[block]
161		{
162			on_idle_full_block::<T>();
163		}
164
165		let checked = (1..=u).rev().collect::<Vec<EraIndex>>();
166		let request = Head::<T>::get().unwrap();
167		assert_eq!(checked, request.checked.into_inner());
168		assert!(matches!(fast_unstake_events::<T>().last(), Some(Event::BatchChecked { .. })));
169		assert!(stashes.iter().all(|(s, _)| request.stashes.iter().any(|(ss, _)| ss == s)));
170	}
171
172	#[benchmark]
173	fn register_fast_unstake() {
174		ErasToCheckPerBlock::<T>::put(1);
175		let who = create_unexposed_batch::<T>(1).get(0).cloned().unwrap();
176		whitelist_account!(who);
177		assert_eq!(Queue::<T>::count(), 0);
178
179		#[extrinsic_call]
180		_(RawOrigin::Signed(who.clone()));
181
182		assert_eq!(Queue::<T>::count(), 1);
183	}
184
185	#[benchmark]
186	fn deregister() {
187		ErasToCheckPerBlock::<T>::put(1);
188		let who = create_unexposed_batch::<T>(1).get(0).cloned().unwrap();
189		assert_ok!(Pallet::<T>::register_fast_unstake(RawOrigin::Signed(who.clone()).into(),));
190		assert_eq!(Queue::<T>::count(), 1);
191		whitelist_account!(who);
192
193		#[extrinsic_call]
194		_(RawOrigin::Signed(who.clone()));
195
196		assert_eq!(Queue::<T>::count(), 0);
197	}
198
199	#[benchmark]
200	fn control() -> Result<(), BenchmarkError> {
201		let origin = <T as Config>::ControlOrigin::try_successful_origin()
202			.map_err(|_| BenchmarkError::Weightless)?;
203
204		#[extrinsic_call]
205		_(origin as T::RuntimeOrigin, T::MaxErasToCheckPerBlock::get());
206
207		Ok(())
208	}
209
210	impl_benchmark_test_suite!(Pallet, mock::ExtBuilder::default().build(), mock::Runtime);
211}