1#![cfg(feature = "runtime-benchmarks")]
21
22use super::*;
23
24use frame_benchmarking::v2::*;
25use frame_system::RawOrigin;
26
27use alloc::vec;
28use sp_runtime::traits::Bounded;
29
30use crate::Pallet as Society;
31
32fn set_block_number<T: Config<I>, I: 'static>(n: BlockNumberFor<T, I>) {
33 <T as Config<I>>::BlockNumberProvider::set_block_number(n);
34}
35
36fn mock_balance_deposit<T: Config<I>, I: 'static>() -> BalanceOf<T, I> {
37 T::Currency::minimum_balance().saturating_mul(1_000u32.into())
38}
39
40fn make_deposit<T: Config<I>, I: 'static>(who: &T::AccountId) -> BalanceOf<T, I> {
41 let amount = mock_balance_deposit::<T, I>();
42 let required = amount.saturating_add(T::Currency::minimum_balance());
43 if T::Currency::free_balance(who) < required {
44 T::Currency::make_free_balance_be(who, required);
45 }
46 T::Currency::reserve(who, amount).expect("Pre-funded account; qed");
47 amount
48}
49
50fn make_bid<T: Config<I>, I: 'static>(
51 who: &T::AccountId,
52) -> BidKind<T::AccountId, BalanceOf<T, I>> {
53 BidKind::Deposit(make_deposit::<T, I>(who))
54}
55
56fn fund_society<T: Config<I>, I: 'static>() {
57 T::Currency::make_free_balance_be(
58 &Society::<T, I>::account_id(),
59 BalanceOf::<T, I>::max_value(),
60 );
61 Pot::<T, I>::put(&BalanceOf::<T, I>::max_value());
62}
63
64fn setup_society<T: Config<I>, I: 'static>() -> Result<T::AccountId, &'static str> {
66 let origin = T::FounderSetOrigin::try_successful_origin().map_err(|_| "No origin")?;
67 let founder: T::AccountId = account("founder", 0, 0);
68 let founder_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(founder.clone());
69 let max_members = 5u32;
70 let max_intake = 3u32;
71 let max_strikes = 3u32;
72 Society::<T, I>::found_society(
73 origin,
74 founder_lookup,
75 max_members,
76 max_intake,
77 max_strikes,
78 mock_balance_deposit::<T, I>(),
79 b"benchmarking-society".to_vec(),
80 )?;
81 T::Currency::make_free_balance_be(
82 &Society::<T, I>::account_id(),
83 T::Currency::minimum_balance(),
84 );
85 T::Currency::make_free_balance_be(&Society::<T, I>::payouts(), T::Currency::minimum_balance());
86 Ok(founder)
87}
88
89fn setup_funded_society<T: Config<I>, I: 'static>() -> Result<T::AccountId, &'static str> {
90 let founder = setup_society::<T, I>()?;
91 fund_society::<T, I>();
92 Ok(founder)
93}
94
95fn add_candidate<T: Config<I>, I: 'static>(
96 name: &'static str,
97 tally: Tally,
98 skeptic_struck: bool,
99) -> T::AccountId {
100 let candidate: T::AccountId = account(name, 0, 0);
101 let candidacy = Candidacy {
102 round: RoundCount::<T, I>::get(),
103 kind: make_bid::<T, I>(&candidate),
104 bid: 0u32.into(),
105 tally,
106 skeptic_struck,
107 };
108 Candidates::<T, I>::insert(&candidate, &candidacy);
109 candidate
110}
111
112fn increment_round<T: Config<I>, I: 'static>() {
113 let mut round_count = RoundCount::<T, I>::get();
114 round_count.saturating_inc();
115 RoundCount::<T, I>::put(round_count);
116}
117
118#[instance_benchmarks]
119mod benchmarks {
120 use super::*;
121
122 #[benchmark]
123 fn bid() -> Result<(), BenchmarkError> {
124 setup_society::<T, I>()?;
125 let caller: T::AccountId = whitelisted_caller();
126 T::Currency::make_free_balance_be(&caller, BalanceOf::<T, I>::max_value());
127
128 #[extrinsic_call]
129 _(RawOrigin::Signed(caller.clone()), 10u32.into());
130
131 let first_bid: Bid<T::AccountId, BalanceOf<T, I>> = Bid {
132 who: caller.clone(),
133 kind: BidKind::Deposit(mock_balance_deposit::<T, I>()),
134 value: 10u32.into(),
135 };
136 assert_eq!(Bids::<T, I>::get(), vec![first_bid]);
137 Ok(())
138 }
139
140 #[benchmark]
141 fn unbid() -> Result<(), BenchmarkError> {
142 setup_society::<T, I>()?;
143 let caller: T::AccountId = whitelisted_caller();
144 T::Currency::make_free_balance_be(&caller, BalanceOf::<T, I>::max_value());
145 let mut bids = Bids::<T, I>::get();
146 Society::<T, I>::insert_bid(&mut bids, &caller, 10u32.into(), make_bid::<T, I>(&caller));
147 Bids::<T, I>::put(bids);
148
149 #[extrinsic_call]
150 _(RawOrigin::Signed(caller.clone()));
151
152 assert_eq!(Bids::<T, I>::get(), vec![]);
153 Ok(())
154 }
155
156 #[benchmark]
157 fn vouch() -> Result<(), BenchmarkError> {
158 setup_society::<T, I>()?;
159 let caller: T::AccountId = whitelisted_caller();
160 let vouched: T::AccountId = account("vouched", 0, 0);
161 T::Currency::make_free_balance_be(&caller, BalanceOf::<T, I>::max_value());
162 let _ = Society::<T, I>::insert_member(&caller, 1u32.into());
163 let vouched_lookup: <T::Lookup as StaticLookup>::Source =
164 T::Lookup::unlookup(vouched.clone());
165
166 #[extrinsic_call]
167 _(RawOrigin::Signed(caller.clone()), vouched_lookup, 0u32.into(), 0u32.into());
168
169 let bids = Bids::<T, I>::get();
170 let vouched_bid: Bid<T::AccountId, BalanceOf<T, I>> = Bid {
171 who: vouched.clone(),
172 kind: BidKind::Vouch(caller.clone(), 0u32.into()),
173 value: 0u32.into(),
174 };
175 assert_eq!(bids, vec![vouched_bid]);
176 Ok(())
177 }
178
179 #[benchmark]
180 fn unvouch() -> Result<(), BenchmarkError> {
181 setup_society::<T, I>()?;
182 let caller: T::AccountId = whitelisted_caller();
183 T::Currency::make_free_balance_be(&caller, BalanceOf::<T, I>::max_value());
184 let mut bids = Bids::<T, I>::get();
185 Society::<T, I>::insert_bid(
186 &mut bids,
187 &caller,
188 10u32.into(),
189 BidKind::Vouch(caller.clone(), 0u32.into()),
190 );
191 Bids::<T, I>::put(bids);
192
193 #[extrinsic_call]
194 _(RawOrigin::Signed(caller.clone()));
195
196 assert_eq!(Bids::<T, I>::get(), vec![]);
197 Ok(())
198 }
199
200 #[benchmark]
201 fn vote() -> Result<(), BenchmarkError> {
202 setup_society::<T, I>()?;
203 let caller: T::AccountId = whitelisted_caller();
204 T::Currency::make_free_balance_be(&caller, BalanceOf::<T, I>::max_value());
205 let _ = Society::<T, I>::insert_member(&caller, 1u32.into());
206 let candidate = add_candidate::<T, I>("candidate", Default::default(), false);
207 let candidate_lookup: <T::Lookup as StaticLookup>::Source =
208 T::Lookup::unlookup(candidate.clone());
209
210 #[extrinsic_call]
211 _(RawOrigin::Signed(caller.clone()), candidate_lookup, true);
212
213 let maybe_vote: Vote = <Votes<T, I>>::get(candidate.clone(), caller).unwrap();
214 assert_eq!(maybe_vote.approve, true);
215 Ok(())
216 }
217
218 #[benchmark]
219 fn defender_vote() -> Result<(), BenchmarkError> {
220 setup_society::<T, I>()?;
221 let caller: T::AccountId = whitelisted_caller();
222 T::Currency::make_free_balance_be(&caller, BalanceOf::<T, I>::max_value());
223 let _ = Society::<T, I>::insert_member(&caller, 1u32.into());
224 let defender: T::AccountId = account("defender", 0, 0);
225 Defending::<T, I>::put((defender, caller.clone(), Tally::default()));
226
227 #[extrinsic_call]
228 _(RawOrigin::Signed(caller.clone()), false);
229
230 let round = RoundCount::<T, I>::get();
231 let skeptic_vote: Vote = DefenderVotes::<T, I>::get(round, &caller).unwrap();
232 assert_eq!(skeptic_vote.approve, false);
233 Ok(())
234 }
235
236 #[benchmark]
237 fn payout() -> Result<(), BenchmarkError> {
238 setup_funded_society::<T, I>()?;
239 let caller: T::AccountId = whitelisted_caller();
241 T::Currency::make_free_balance_be(&caller, mock_balance_deposit::<T, I>());
242 let _ = Society::<T, I>::insert_member(&caller, 0u32.into());
243 Society::<T, I>::bump_payout(&caller, 0u32.into(), 1u32.into());
245
246 #[extrinsic_call]
247 _(RawOrigin::Signed(caller.clone()));
248
249 let record = Payouts::<T, I>::get(caller);
250 assert!(record.payouts.is_empty());
251 Ok(())
252 }
253
254 #[benchmark]
255 fn waive_repay() -> Result<(), BenchmarkError> {
256 setup_funded_society::<T, I>()?;
257 let caller: T::AccountId = whitelisted_caller();
258 T::Currency::make_free_balance_be(&caller, BalanceOf::<T, I>::max_value());
259 let _ = Society::<T, I>::insert_member(&caller, 0u32.into());
260 Society::<T, I>::bump_payout(&caller, 0u32.into(), 1u32.into());
261
262 #[extrinsic_call]
263 _(RawOrigin::Signed(caller.clone()), 1u32.into());
264
265 let record = Payouts::<T, I>::get(caller);
266 assert!(record.payouts.is_empty());
267 Ok(())
268 }
269
270 #[benchmark]
271 fn found_society() -> Result<(), BenchmarkError> {
272 let founder: T::AccountId = whitelisted_caller();
273 let can_found = T::FounderSetOrigin::try_successful_origin().map_err(|_| "No origin")?;
274 let founder_lookup: <T::Lookup as StaticLookup>::Source =
275 T::Lookup::unlookup(founder.clone());
276
277 #[extrinsic_call]
278 _(
279 can_found as T::RuntimeOrigin,
280 founder_lookup,
281 5,
282 3,
283 3,
284 mock_balance_deposit::<T, I>(),
285 b"benchmarking-society".to_vec(),
286 );
287
288 assert_eq!(Founder::<T, I>::get(), Some(founder.clone()));
289 Ok(())
290 }
291
292 #[benchmark]
293 fn dissolve() -> Result<(), BenchmarkError> {
294 let founder = setup_society::<T, I>()?;
295 let members_and_candidates = vec![("m1", "c1"), ("m2", "c2"), ("m3", "c3"), ("m4", "c4")];
296 let members_count = members_and_candidates.clone().len() as u32;
297 for (m, c) in members_and_candidates {
298 let member: T::AccountId = account(m, 0, 0);
299 let _ = Society::<T, I>::insert_member(&member, 100u32.into());
300 let candidate = add_candidate::<T, I>(
301 c,
302 Tally { approvals: 1u32.into(), rejections: 1u32.into() },
303 false,
304 );
305 let candidate_lookup: <T::Lookup as StaticLookup>::Source =
306 T::Lookup::unlookup(candidate);
307 let _ = Society::<T, I>::vote(RawOrigin::Signed(member).into(), candidate_lookup, true);
308 }
309 MemberCount::<T, I>::mutate(|i| i.saturating_reduce(members_count));
311
312 #[extrinsic_call]
313 _(RawOrigin::Signed(founder));
314
315 assert_eq!(Founder::<T, I>::get(), None);
316 Ok(())
317 }
318
319 #[benchmark]
320 fn judge_suspended_member() -> Result<(), BenchmarkError> {
321 let founder = setup_society::<T, I>()?;
322 let caller: T::AccountId = whitelisted_caller();
323 let caller_lookup: <T::Lookup as StaticLookup>::Source =
324 T::Lookup::unlookup(caller.clone());
325 let _ = Society::<T, I>::insert_member(&caller, 0u32.into());
326 let _ = Society::<T, I>::suspend_member(&caller);
327
328 #[extrinsic_call]
329 _(RawOrigin::Signed(founder), caller_lookup, false);
330
331 assert_eq!(SuspendedMembers::<T, I>::contains_key(&caller), false);
332 Ok(())
333 }
334
335 #[benchmark]
336 fn set_parameters() -> Result<(), BenchmarkError> {
337 let founder = setup_society::<T, I>()?;
338 let max_members = 10u32;
339 let max_intake = 10u32;
340 let max_strikes = 10u32;
341 let candidate_deposit: BalanceOf<T, I> = 10u32.into();
342 let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit };
343
344 #[extrinsic_call]
345 _(RawOrigin::Signed(founder), max_members, max_intake, max_strikes, candidate_deposit);
346
347 assert_eq!(Parameters::<T, I>::get(), Some(params));
348 Ok(())
349 }
350
351 #[benchmark]
352 fn punish_skeptic() -> Result<(), BenchmarkError> {
353 setup_society::<T, I>()?;
354 let candidate = add_candidate::<T, I>("candidate", Default::default(), false);
355 let skeptic: T::AccountId = account("skeptic", 0, 0);
356 let _ = Society::<T, I>::insert_member(&skeptic, 0u32.into());
357 Skeptic::<T, I>::put(&skeptic);
358 if let Period::Voting { more, .. } = Society::<T, I>::period() {
359 set_block_number::<T, I>(T::BlockNumberProvider::current_block_number() + more)
360 }
361
362 #[extrinsic_call]
363 _(RawOrigin::Signed(candidate.clone()));
364
365 let candidacy = Candidates::<T, I>::get(&candidate).unwrap();
366 assert_eq!(candidacy.skeptic_struck, true);
367 Ok(())
368 }
369
370 #[benchmark]
371 fn claim_membership() -> Result<(), BenchmarkError> {
372 setup_society::<T, I>()?;
373 let candidate = add_candidate::<T, I>(
374 "candidate",
375 Tally { approvals: 3u32.into(), rejections: 0u32.into() },
376 false,
377 );
378 increment_round::<T, I>();
379
380 #[extrinsic_call]
381 _(RawOrigin::Signed(candidate.clone()));
382
383 assert!(!Candidates::<T, I>::contains_key(&candidate));
384 assert!(Members::<T, I>::contains_key(&candidate));
385 Ok(())
386 }
387
388 #[benchmark]
389 fn bestow_membership() -> Result<(), BenchmarkError> {
390 let founder = setup_society::<T, I>()?;
391 let candidate = add_candidate::<T, I>(
392 "candidate",
393 Tally { approvals: 3u32.into(), rejections: 1u32.into() },
394 false,
395 );
396 increment_round::<T, I>();
397
398 #[extrinsic_call]
399 _(RawOrigin::Signed(founder), candidate.clone());
400
401 assert!(!Candidates::<T, I>::contains_key(&candidate));
402 assert!(Members::<T, I>::contains_key(&candidate));
403 Ok(())
404 }
405
406 #[benchmark]
407 fn kick_candidate() -> Result<(), BenchmarkError> {
408 let founder = setup_society::<T, I>()?;
409 let candidate = add_candidate::<T, I>(
410 "candidate",
411 Tally { approvals: 1u32.into(), rejections: 1u32.into() },
412 false,
413 );
414 increment_round::<T, I>();
415
416 #[extrinsic_call]
417 _(RawOrigin::Signed(founder), candidate.clone());
418
419 assert!(!Candidates::<T, I>::contains_key(&candidate));
420 Ok(())
421 }
422
423 #[benchmark]
424 fn resign_candidacy() -> Result<(), BenchmarkError> {
425 setup_society::<T, I>()?;
426 let candidate = add_candidate::<T, I>(
427 "candidate",
428 Tally { approvals: 0u32.into(), rejections: 0u32.into() },
429 false,
430 );
431
432 #[extrinsic_call]
433 _(RawOrigin::Signed(candidate.clone()));
434
435 assert!(!Candidates::<T, I>::contains_key(&candidate));
436 Ok(())
437 }
438
439 #[benchmark]
440 fn drop_candidate() -> Result<(), BenchmarkError> {
441 setup_society::<T, I>()?;
442 let candidate = add_candidate::<T, I>(
443 "candidate",
444 Tally { approvals: 0u32.into(), rejections: 3u32.into() },
445 false,
446 );
447 let caller: T::AccountId = whitelisted_caller();
448 let _ = Society::<T, I>::insert_member(&caller, 0u32.into());
449 let mut round_count = RoundCount::<T, I>::get();
450 round_count = round_count.saturating_add(2u32);
451 RoundCount::<T, I>::put(round_count);
452
453 #[extrinsic_call]
454 _(RawOrigin::Signed(caller), candidate.clone());
455
456 assert!(!Candidates::<T, I>::contains_key(&candidate));
457 Ok(())
458 }
459
460 #[benchmark]
461 fn cleanup_candidacy() -> Result<(), BenchmarkError> {
462 setup_society::<T, I>()?;
463 let candidate = add_candidate::<T, I>(
464 "candidate",
465 Tally { approvals: 0u32.into(), rejections: 0u32.into() },
466 false,
467 );
468 let member_one: T::AccountId = account("one", 0, 0);
469 let member_two: T::AccountId = account("two", 0, 0);
470 let _ = Society::<T, I>::insert_member(&member_one, 0u32.into());
471 let _ = Society::<T, I>::insert_member(&member_two, 0u32.into());
472 let candidate_lookup: <T::Lookup as StaticLookup>::Source =
473 T::Lookup::unlookup(candidate.clone());
474 let _ = Society::<T, I>::vote(
475 RawOrigin::Signed(member_one.clone()).into(),
476 candidate_lookup.clone(),
477 true,
478 );
479 let _ = Society::<T, I>::vote(
480 RawOrigin::Signed(member_two.clone()).into(),
481 candidate_lookup,
482 true,
483 );
484 Candidates::<T, I>::remove(&candidate);
485
486 #[extrinsic_call]
487 _(RawOrigin::Signed(member_one), candidate.clone(), 5);
488
489 assert_eq!(Votes::<T, I>::get(&candidate, &member_two), None);
490 Ok(())
491 }
492
493 #[benchmark]
494 fn cleanup_challenge() -> Result<(), BenchmarkError> {
495 setup_society::<T, I>()?;
496 ChallengeRoundCount::<T, I>::put(1u32);
497 let member: T::AccountId = whitelisted_caller();
498 let _ = Society::<T, I>::insert_member(&member, 0u32.into());
499 let defender: T::AccountId = account("defender", 0, 0);
500 Defending::<T, I>::put((defender.clone(), member.clone(), Tally::default()));
501 let _ = Society::<T, I>::defender_vote(RawOrigin::Signed(member.clone()).into(), true);
502 ChallengeRoundCount::<T, I>::put(2u32);
503 let mut challenge_round = ChallengeRoundCount::<T, I>::get();
504 challenge_round = challenge_round.saturating_sub(1u32);
505
506 #[extrinsic_call]
507 _(RawOrigin::Signed(member.clone()), challenge_round, 1u32);
508
509 assert_eq!(DefenderVotes::<T, I>::get(challenge_round, &defender), None);
510 Ok(())
511 }
512
513 #[benchmark]
514 fn poke_deposit() -> Result<(), BenchmarkError> {
515 setup_society::<T, I>()?;
517 let bidder: T::AccountId = whitelisted_caller();
518 T::Currency::make_free_balance_be(&bidder, BalanceOf::<T, I>::max_value());
519
520 let initial_deposit = mock_balance_deposit::<T, I>();
522 Society::<T, I>::bid(RawOrigin::Signed(bidder.clone()).into(), 0u32.into())?;
523
524 assert_eq!(T::Currency::reserved_balance(&bidder), initial_deposit);
526 let bids = Bids::<T, I>::get();
527 let existing_bid = bids.iter().find(|b| b.who == bidder).expect("Bid should exist");
528 assert_eq!(existing_bid.kind, BidKind::Deposit(initial_deposit));
529
530 let extra_amount = 2u32.into();
532 let increased_deposit = initial_deposit.saturating_add(extra_amount);
533 Bids::<T, I>::try_mutate(|bids| -> Result<(), BenchmarkError> {
534 if let Some(existing_bid) = bids.iter_mut().find(|b| b.who == bidder) {
535 existing_bid.kind = BidKind::Deposit(increased_deposit);
536 Ok(())
537 } else {
538 Err(BenchmarkError::Stop("Bid not found"))
539 }
540 })?;
541 T::Currency::reserve(&bidder, extra_amount)?;
542
543 assert_eq!(T::Currency::reserved_balance(&bidder), increased_deposit);
545 let bids = Bids::<T, I>::get();
546 let existing_bid = bids.iter().find(|b| b.who == bidder).expect("Bid should exist");
547 assert_eq!(existing_bid.kind, BidKind::Deposit(increased_deposit));
548
549 #[extrinsic_call]
550 _(RawOrigin::Signed(bidder.clone()));
551
552 assert_eq!(T::Currency::reserved_balance(&bidder), initial_deposit);
554 let bids = Bids::<T, I>::get();
555 let existing_bid = bids.iter().find(|b| b.who == bidder).expect("Bid should exist");
556 assert_eq!(existing_bid.kind, BidKind::Deposit(initial_deposit));
557
558 Ok(())
559 }
560
561 impl_benchmark_test_suite!(
562 Society,
563 sp_io::TestExternalities::from(
564 <frame_system::GenesisConfig::<crate::mock::Test> as sp_runtime::BuildStorage>::build_storage(
565 &frame_system::GenesisConfig::default()).unwrap()
566 ),
567 crate::mock::Test
568 );
569}