pallet_fast_unstake/
benchmarking.rs1#![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 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 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 #[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 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 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::<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 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}