referrerpolicy=no-referrer-when-downgrade

pallet_society/
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//! Society pallet benchmarking.
19
20#![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
64// Set up Society
65fn 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		// Payee's account already exists and is a member.
240		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		// Introduce payout.
244		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		// Leaving only Founder member.
310		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		// Set up society
516		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		// Make initial bid
521		let initial_deposit = mock_balance_deposit::<T, I>();
522		Society::<T, I>::bid(RawOrigin::Signed(bidder.clone()).into(), 0u32.into())?;
523
524		// Verify initial state
525		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		// Artificially increase deposit in storage and reserve extra balance
531		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		// Verify increased state
544		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		// Verify final state returned to initial deposit
553		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}