pallet_election_provider_multi_block/signed/
benchmarking.rs1use crate::{
19 signed::{Config, Invulnerables, Pallet, RewardSource, Submissions, MAX_UNPAID_REWARDS},
20 types::PagedRawSolution,
21 unsigned::miner::OffchainWorkerMiner,
22 CurrentPhase, Phase, Round,
23};
24use frame_benchmarking::v2::*;
25use frame_election_provider_support::ElectionProvider;
26use frame_support::{
27 pallet_prelude::*,
28 traits::fungible::{Inspect, Mutate},
29};
30use frame_system::RawOrigin;
31use sp_npos_elections::ElectionScore;
32use sp_runtime::traits::{One, Saturating};
33use sp_std::boxed::Box;
34
35#[benchmarks(where T: crate::Config + crate::verifier::Config + crate::unsigned::Config)]
36mod benchmarks {
37 use super::*;
38
39 #[benchmark(pov_mode = Measured)]
40 fn register_not_full() -> Result<(), BenchmarkError> {
41 CurrentPhase::<T>::put(Phase::Signed(T::SignedPhase::get() - One::one()));
42 let round = Round::<T>::get();
43 let alice = crate::Pallet::<T>::funded_account("alice", 0);
44 let score = ElectionScore::default();
45
46 assert_eq!(Submissions::<T>::sorted_submitters(round).len(), 0);
47 #[block]
48 {
49 Pallet::<T>::register(RawOrigin::Signed(alice).into(), score)?;
50 }
51
52 assert_eq!(Submissions::<T>::sorted_submitters(round).len(), 1);
53 Ok(())
54 }
55
56 #[benchmark(pov_mode = Measured)]
57 fn register_eject() -> Result<(), BenchmarkError> {
58 CurrentPhase::<T>::put(Phase::Signed(T::SignedPhase::get() - One::one()));
59 let round = Round::<T>::get();
60
61 for i in 0..T::MaxSubmissions::get() {
62 let submitter = crate::Pallet::<T>::funded_account("submitter", i);
63 let score = ElectionScore { minimal_stake: i.into(), ..Default::default() };
64 Pallet::<T>::register(RawOrigin::Signed(submitter.clone()).into(), score)?;
65
66 if i == 0 {
68 for p in 0..T::Pages::get() {
69 let page = Some(Default::default());
70 Pallet::<T>::submit_page(RawOrigin::Signed(submitter.clone()).into(), p, page)?;
71 }
72 }
73 }
74
75 let who = crate::Pallet::<T>::funded_account("who", 0);
76 let score =
77 ElectionScore { minimal_stake: T::MaxSubmissions::get().into(), ..Default::default() };
78
79 assert_eq!(
80 Submissions::<T>::sorted_submitters(round).len(),
81 T::MaxSubmissions::get() as usize
82 );
83
84 #[block]
85 {
86 Pallet::<T>::register(RawOrigin::Signed(who).into(), score)?;
87 }
88
89 assert_eq!(
90 Submissions::<T>::sorted_submitters(round).len(),
91 T::MaxSubmissions::get() as usize
92 );
93 Ok(())
94 }
95
96 #[benchmark(pov_mode = Measured)]
97 fn submit_page() -> Result<(), BenchmarkError> {
98 #[cfg(test)]
99 crate::mock::ElectionStart::set(sp_runtime::traits::Bounded::max_value());
100 crate::Pallet::<T>::start().unwrap();
101
102 crate::Pallet::<T>::roll_until_matches(|| {
103 matches!(CurrentPhase::<T>::get(), Phase::Signed(_))
104 });
105
106 let PagedRawSolution { score, solution_pages, .. } =
108 OffchainWorkerMiner::<T>::mine_solution(T::Pages::get(), false).unwrap();
109 let page = Some(Box::new(solution_pages[0].clone()));
110
111 let alice = crate::Pallet::<T>::funded_account("alice", 0);
113 Pallet::<T>::register(RawOrigin::Signed(alice.clone()).into(), score)?;
114
115 #[block]
116 {
117 Pallet::<T>::submit_page(RawOrigin::Signed(alice).into(), 0, page)?;
118 }
119
120 Ok(())
121 }
122
123 #[benchmark(pov_mode = Measured)]
124 fn unset_page() -> Result<(), BenchmarkError> {
125 #[cfg(test)]
126 crate::mock::ElectionStart::set(sp_runtime::traits::Bounded::max_value());
127 crate::Pallet::<T>::start().unwrap();
128
129 crate::Pallet::<T>::roll_until_matches(|| {
130 matches!(CurrentPhase::<T>::get(), Phase::Signed(_))
131 });
132
133 let PagedRawSolution { score, solution_pages, .. } =
135 OffchainWorkerMiner::<T>::mine_solution(T::Pages::get(), false).unwrap();
136 let page = Some(Box::new(solution_pages[0].clone()));
137
138 let alice = crate::Pallet::<T>::funded_account("alice", 0);
140 Pallet::<T>::register(RawOrigin::Signed(alice.clone()).into(), score)?;
141
142 Pallet::<T>::submit_page(RawOrigin::Signed(alice.clone()).into(), 0, page)?;
144
145 #[block]
146 {
147 Pallet::<T>::submit_page(RawOrigin::Signed(alice).into(), 0, None)?;
148 }
149
150 Ok(())
151 }
152
153 #[benchmark(pov_mode = Measured)]
154 fn bail() -> Result<(), BenchmarkError> {
155 CurrentPhase::<T>::put(Phase::Signed(T::SignedPhase::get() - One::one()));
156 let alice = crate::Pallet::<T>::funded_account("alice", 0);
157
158 let score = ElectionScore::default();
160 Pallet::<T>::register(RawOrigin::Signed(alice.clone()).into(), score)?;
161
162 for p in 0..T::Pages::get() {
164 let page = Some(Default::default());
165 Pallet::<T>::submit_page(RawOrigin::Signed(alice.clone()).into(), p, page)?;
166 }
167
168 #[block]
169 {
170 Pallet::<T>::bail(RawOrigin::Signed(alice).into())?;
171 }
172
173 Ok(())
174 }
175
176 #[benchmark(pov_mode = Measured)]
177 fn clear_old_round_data(p: Linear<1, { T::Pages::get() }>) -> Result<(), BenchmarkError> {
178 CurrentPhase::<T>::put(Phase::Signed(T::SignedPhase::get() - One::one()));
180 let alice = crate::Pallet::<T>::funded_account("alice", 0);
181
182 Invulnerables::<T>::put(BoundedVec::truncate_from(sp_std::vec![alice.clone()]));
184
185 let score = ElectionScore::default();
187 Pallet::<T>::register(RawOrigin::Signed(alice.clone()).into(), score)?;
188
189 for pp in 0..p {
191 let page = Some(Default::default());
192 Pallet::<T>::submit_page(RawOrigin::Signed(alice.clone()).into(), pp, page)?;
193 }
194
195 let prev_round = Round::<T>::get();
197 crate::Pallet::<T>::rotate_round();
198
199 let source_and_balance_before = if let Some(source) = T::RewardSource::account() {
200 let funds =
201 <T as Config>::MaxFeeRefund::get().saturating_add(T::Currency::minimum_balance());
202 T::Currency::mint_into(&source, funds)?;
203 Some((source.clone(), T::Currency::balance(&source)))
204 } else {
205 None
206 };
207
208 #[block]
209 {
210 Pallet::<T>::clear_old_round_data(RawOrigin::Signed(alice).into(), prev_round, p)?;
211 }
212
213 if let Some((source, balance_before)) = source_and_balance_before {
214 assert!(
215 T::Currency::balance(&source) < balance_before,
216 "fee refund must have transferred out of the configured RewardSource pot"
217 );
218 }
219 Ok(())
220 }
221
222 #[benchmark(pov_mode = Measured)]
223 fn claim_unpaid_reward() -> Result<(), BenchmarkError> {
224 for i in 0..MAX_UNPAID_REWARDS {
226 let who = crate::Pallet::<T>::funded_account("filler", i);
227 let entry = crate::signed::UnpaidReward::<T> {
228 round: i,
229 who,
230 amount: <T as Config>::RewardBase::get(),
231 };
232 crate::signed::UnpaidRewards::<T>::try_mutate(|unpaid| unpaid.try_push(entry))
233 .map_err(|_| BenchmarkError::Stop("UnpaidRewards is full"))?;
234 }
235 let target_round = MAX_UNPAID_REWARDS - 1;
236
237 let source_and_balance_before = if let Some(source) = T::RewardSource::account() {
241 let funds =
242 <T as Config>::RewardBase::get().saturating_add(T::Currency::minimum_balance());
243 T::Currency::mint_into(&source, funds)?;
244 Some((source.clone(), T::Currency::balance(&source)))
245 } else {
246 None
247 };
248
249 let caller = crate::Pallet::<T>::funded_account("caller", 0);
250
251 #[block]
252 {
253 Pallet::<T>::claim_unpaid_reward(RawOrigin::Signed(caller).into(), target_round)?;
254 }
255
256 assert_eq!(crate::signed::UnpaidRewards::<T>::get().len(), MAX_UNPAID_REWARDS as usize - 1);
257 if let Some((source, balance_before)) = source_and_balance_before {
260 assert!(
261 T::Currency::balance(&source) < balance_before,
262 "claim must have transferred out of the configured RewardSource pot"
263 );
264 }
265 Ok(())
266 }
267
268 impl_benchmark_test_suite!(
269 Pallet,
270 crate::mock::ExtBuilder::signed().build_unchecked(),
271 crate::mock::Runtime
272 );
273}