pallet_elections_phragmen/
benchmarking.rs1#![cfg(feature = "runtime-benchmarks")]
21
22use frame_benchmarking::v2::*;
23use frame_support::{dispatch::DispatchResultWithPostInfo, traits::OnInitialize};
24use frame_system::RawOrigin;
25
26#[cfg(test)]
27use crate::tests::MEMBERS;
28use crate::*;
29
30const BALANCE_FACTOR: u32 = 250;
31
32fn endowed_account<T: Config>(name: &'static str, index: u32) -> T::AccountId {
34 let account: T::AccountId = account(name, index, 0);
35 let amount = default_stake::<T>(T::MaxVoters::get()) * BalanceOf::<T>::from(BALANCE_FACTOR);
38 let _ = T::Currency::make_free_balance_be(&account, amount);
39 let _ = T::Currency::issue(amount);
42
43 account
44}
45
46fn as_lookup<T: Config>(account: T::AccountId) -> AccountIdLookupOf<T> {
48 T::Lookup::unlookup(account)
49}
50
51fn default_stake<T: Config>(num_votes: u32) -> BalanceOf<T> {
53 let min = T::Currency::minimum_balance();
54 Pallet::<T>::deposit_of(num_votes as usize).max(min)
55}
56
57fn candidate_count<T: Config>() -> u32 {
59 Candidates::<T>::decode_len().unwrap_or(0usize) as u32
60}
61
62fn submit_candidates<T: Config>(
64 c: u32,
65 prefix: &'static str,
66) -> Result<Vec<T::AccountId>, &'static str> {
67 (0..c)
68 .map(|i| {
69 let account = endowed_account::<T>(prefix, i);
70 Pallet::<T>::submit_candidacy(
71 RawOrigin::Signed(account.clone()).into(),
72 candidate_count::<T>(),
73 )
74 .map_err(|_| "failed to submit candidacy")?;
75 Ok(account)
76 })
77 .collect::<Result<_, _>>()
78}
79
80fn submit_candidates_with_self_vote<T: Config>(
82 c: u32,
83 prefix: &'static str,
84) -> Result<Vec<T::AccountId>, &'static str> {
85 let candidates = submit_candidates::<T>(c, prefix)?;
86 let stake = default_stake::<T>(c);
87 candidates
88 .iter()
89 .try_for_each(|c| submit_voter::<T>(c.clone(), vec![c.clone()], stake).map(|_| ()))?;
90 Ok(candidates)
91}
92
93fn submit_voter<T: Config>(
95 caller: T::AccountId,
96 votes: Vec<T::AccountId>,
97 stake: BalanceOf<T>,
98) -> DispatchResultWithPostInfo {
99 Pallet::<T>::vote(RawOrigin::Signed(caller).into(), votes, stake)
100}
101
102fn distribute_voters<T: Config>(
105 mut all_candidates: Vec<T::AccountId>,
106 num_voters: u32,
107 votes: usize,
108) -> Result<(), &'static str> {
109 let stake = default_stake::<T>(num_voters);
110 for i in 0..num_voters {
111 all_candidates.rotate_left(1);
113 let votes = all_candidates.iter().cloned().take(votes).collect::<Vec<_>>();
114 let voter = endowed_account::<T>("voter", i);
115 submit_voter::<T>(voter, votes, stake)?;
116 }
117 Ok(())
118}
119
120fn fill_seats_up_to<T: Config>(m: u32) -> Result<Vec<T::AccountId>, &'static str> {
123 submit_candidates_with_self_vote::<T>(m, "fill_seats_up_to")?;
124 assert_eq!(Candidates::<T>::get().len() as u32, m, "wrong number of candidates.");
125 Pallet::<T>::do_phragmen();
126 assert_eq!(Candidates::<T>::get().len(), 0, "some candidates remaining.");
127 assert_eq!(
128 Members::<T>::get().len() + RunnersUp::<T>::get().len(),
129 m as usize,
130 "wrong number of members and runners-up",
131 );
132 Ok(Members::<T>::get()
133 .into_iter()
134 .map(|m| m.who)
135 .chain(RunnersUp::<T>::get().into_iter().map(|r| r.who))
136 .collect())
137}
138
139fn clean<T: Config>() {
141 Members::<T>::kill();
142 Candidates::<T>::kill();
143 RunnersUp::<T>::kill();
144 #[allow(deprecated)]
145 Voting::<T>::remove_all(None);
146}
147
148#[benchmarks]
149mod benchmarks {
150 use super::*;
151
152 #[benchmark]
154 fn vote_equal(v: Linear<1, { T::MaxVotesPerVoter::get() }>) -> Result<(), BenchmarkError> {
155 clean::<T>();
156
157 let all_candidates = submit_candidates::<T>(v, "candidates")?;
159
160 let caller = endowed_account::<T>("caller", 0);
161 let stake = default_stake::<T>(v);
162
163 let mut votes = all_candidates;
165 submit_voter::<T>(caller.clone(), votes.clone(), stake)?;
166
167 votes.rotate_left(1);
169
170 whitelist!(caller);
171
172 #[extrinsic_call]
173 vote(RawOrigin::Signed(caller), votes, stake);
174
175 Ok(())
176 }
177
178 #[benchmark]
179 fn vote_more(v: Linear<2, { T::MaxVotesPerVoter::get() }>) -> Result<(), BenchmarkError> {
180 clean::<T>();
181
182 let all_candidates = submit_candidates::<T>(v, "candidates")?;
184
185 let caller = endowed_account::<T>("caller", 0);
186 let stake = default_stake::<T>(v) * BalanceOf::<T>::from(10_u32);
188
189 let mut votes = all_candidates.iter().skip(1).cloned().collect::<Vec<_>>();
191 submit_voter::<T>(caller.clone(), votes.clone(), stake / BalanceOf::<T>::from(10_u32))?;
192
193 votes = all_candidates;
195 assert!(votes.len() > Voting::<T>::get(caller.clone()).votes.len());
196
197 whitelist!(caller);
198
199 #[extrinsic_call]
200 vote(RawOrigin::Signed(caller), votes, stake / BalanceOf::<T>::from(10_u32));
201
202 Ok(())
203 }
204
205 #[benchmark]
206 fn vote_less(v: Linear<2, { T::MaxVotesPerVoter::get() }>) -> Result<(), BenchmarkError> {
207 clean::<T>();
208
209 let all_candidates = submit_candidates::<T>(v, "candidates")?;
211
212 let caller = endowed_account::<T>("caller", 0);
213 let stake = default_stake::<T>(v);
214
215 let mut votes = all_candidates;
217 submit_voter::<T>(caller.clone(), votes.clone(), stake)?;
218
219 votes = votes.into_iter().skip(1).collect::<Vec<_>>();
221 assert!(votes.len() < Voting::<T>::get(caller.clone()).votes.len());
222
223 whitelist!(caller);
224
225 #[extrinsic_call]
226 vote(RawOrigin::Signed(caller), votes, stake);
227
228 Ok(())
229 }
230
231 #[benchmark]
232 fn remove_voter() -> Result<(), BenchmarkError> {
233 let v = T::MaxVotesPerVoter::get();
235 clean::<T>();
236
237 let all_candidates = submit_candidates::<T>(v, "candidates")?;
239
240 let caller = endowed_account::<T>("caller", 0);
241
242 let stake = default_stake::<T>(v);
243 submit_voter::<T>(caller.clone(), all_candidates, stake)?;
244
245 whitelist!(caller);
246
247 #[extrinsic_call]
248 _(RawOrigin::Signed(caller));
249
250 Ok(())
251 }
252
253 #[benchmark]
254 fn submit_candidacy(
255 c: Linear<1, { T::MaxCandidates::get() }>,
257 ) -> Result<(), BenchmarkError> {
258 let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get();
261
262 clean::<T>();
263
264 fill_seats_up_to::<T>(m)?;
266
267 submit_candidates::<T>(c, "candidates")?;
269
270 let candidate_account = endowed_account::<T>("caller", 0);
272 whitelist!(candidate_account);
273
274 #[extrinsic_call]
275 _(RawOrigin::Signed(candidate_account), candidate_count::<T>());
276
277 #[cfg(test)]
279 MEMBERS.with(|m| *m.borrow_mut() = vec![]);
280
281 Ok(())
282 }
283
284 #[benchmark]
285 fn renounce_candidacy_candidate(
286 c: Linear<1, { T::MaxCandidates::get() }>,
291 ) -> Result<(), BenchmarkError> {
292 let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get();
295
296 clean::<T>();
297
298 fill_seats_up_to::<T>(m)?;
300 let all_candidates = submit_candidates::<T>(c, "caller")?;
301
302 let bailing = all_candidates[0].clone(); let count = candidate_count::<T>();
304 whitelist!(bailing);
305
306 #[extrinsic_call]
307 renounce_candidacy(RawOrigin::Signed(bailing), Renouncing::Candidate(count));
308
309 #[cfg(test)]
311 MEMBERS.with(|m| *m.borrow_mut() = vec![]);
312
313 Ok(())
314 }
315
316 #[benchmark]
317 fn renounce_candidacy_members() -> Result<(), BenchmarkError> {
318 let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get();
322 clean::<T>();
323
324 let members_and_runners_up = fill_seats_up_to::<T>(m)?;
326
327 let bailing = members_and_runners_up[0].clone();
328 assert!(Pallet::<T>::is_member(&bailing));
329
330 whitelist!(bailing);
331
332 #[extrinsic_call]
333 renounce_candidacy(RawOrigin::Signed(bailing.clone()), Renouncing::Member);
334
335 #[cfg(test)]
337 MEMBERS.with(|m| *m.borrow_mut() = vec![]);
338
339 Ok(())
340 }
341
342 #[benchmark]
343 fn renounce_candidacy_runners_up() -> Result<(), BenchmarkError> {
344 let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get();
348 clean::<T>();
349
350 let members_and_runners_up = fill_seats_up_to::<T>(m)?;
352
353 let bailing = members_and_runners_up[T::DesiredMembers::get() as usize + 1].clone();
354 assert!(Pallet::<T>::is_runner_up(&bailing));
355
356 whitelist!(bailing);
357
358 #[extrinsic_call]
359 renounce_candidacy(RawOrigin::Signed(bailing.clone()), Renouncing::RunnerUp);
360
361 #[cfg(test)]
363 MEMBERS.with(|m| *m.borrow_mut() = vec![]);
364
365 Ok(())
366 }
367
368 #[benchmark]
370 fn remove_member_without_replacement() -> Result<(), BenchmarkError> {
371 #[block]
372 {
373 Err(BenchmarkError::Override(BenchmarkResult::from_weight(
374 T::BlockWeights::get().max_block,
375 )))?;
376 }
377
378 Ok(())
379 }
380
381 #[benchmark]
382 fn remove_member_with_replacement() -> Result<(), BenchmarkError> {
383 let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get();
389 clean::<T>();
390
391 fill_seats_up_to::<T>(m)?;
392 let removing = as_lookup::<T>(Pallet::<T>::members_ids()[0].clone());
393
394 #[extrinsic_call]
395 remove_member(RawOrigin::Root, removing, true, false);
396
397 assert_eq!(Members::<T>::get().len() as u32, T::DesiredMembers::get());
399
400 #[cfg(test)]
402 MEMBERS.with(|m| *m.borrow_mut() = vec![]);
403
404 Ok(())
405 }
406
407 #[benchmark]
408 fn clean_defunct_voters(
409 v: Linear<{ T::MaxVoters::get() / 2 }, { T::MaxVoters::get() }>,
411 d: Linear<0, { T::MaxVoters::get() / 2 }>,
413 ) -> Result<(), BenchmarkError> {
414 clean::<T>();
416
417 let all_candidates = submit_candidates::<T>(T::MaxCandidates::get(), "candidates")?;
418 distribute_voters::<T>(all_candidates, v, T::MaxVotesPerVoter::get() as usize)?;
419
420 Candidates::<T>::kill();
422
423 assert!(Voting::<T>::iter().all(|(_, v)| Pallet::<T>::is_defunct_voter(&v.votes)));
425 assert_eq!(Voting::<T>::iter().count() as u32, v);
426
427 #[extrinsic_call]
428 _(RawOrigin::Root, v, d);
429
430 assert_eq!(Voting::<T>::iter().count() as u32, v - d);
431
432 Ok(())
433 }
434
435 #[benchmark]
436 fn election_phragmen(
437 c: Linear<1, { T::MaxCandidates::get() }>,
443 v: Linear<1, { T::MaxVoters::get() }>,
444 e: Linear<{ T::MaxVoters::get() }, { T::MaxVoters::get() * T::MaxVotesPerVoter::get() }>,
445 ) -> Result<(), BenchmarkError> {
446 clean::<T>();
447
448 let votes_per_voter = (e / v).min(T::MaxVotesPerVoter::get());
458
459 let all_candidates = submit_candidates_with_self_vote::<T>(c, "candidates")?;
460 distribute_voters::<T>(all_candidates, v.saturating_sub(c), votes_per_voter as usize)?;
461
462 #[block]
463 {
464 Pallet::<T>::on_initialize(T::TermDuration::get());
465 }
466
467 assert_eq!(Members::<T>::get().len() as u32, T::DesiredMembers::get().min(c));
468 assert_eq!(
469 RunnersUp::<T>::get().len() as u32,
470 T::DesiredRunnersUp::get().min(c.saturating_sub(T::DesiredMembers::get())),
471 );
472
473 #[cfg(test)]
475 MEMBERS.with(|m| *m.borrow_mut() = vec![]);
476
477 Ok(())
478 }
479
480 impl_benchmark_test_suite! {
481 Pallet,
482 tests::ExtBuilder::default().desired_members(13).desired_runners_up(7),
483 tests::Test,
484 exec_name = build_and_execute,
485 }
486}