referrerpolicy=no-referrer-when-downgrade

pallet_child_bounties/
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//! Child-bounties pallet benchmarking.
19
20#![cfg(feature = "runtime-benchmarks")]
21
22use alloc::vec;
23use frame_benchmarking::{v2::*, BenchmarkError};
24use frame_support::ensure;
25use frame_system::RawOrigin;
26use pallet_bounties::Pallet as Bounties;
27use pallet_treasury::Pallet as Treasury;
28use sp_runtime::traits::BlockNumberProvider;
29
30use crate::*;
31
32const SEED: u32 = 0;
33
34#[derive(Clone)]
35struct BenchmarkChildBounty<T: Config> {
36	/// Bounty ID.
37	bounty_id: BountyIndex,
38	/// ChildBounty ID.
39	child_bounty_id: BountyIndex,
40	/// The account proposing it.
41	caller: T::AccountId,
42	/// The master curator account.
43	curator: T::AccountId,
44	/// The child-bounty curator account.
45	child_curator: T::AccountId,
46	/// The (total) amount that should be paid if the bounty is rewarded.
47	value: BalanceOf<T>,
48	/// The curator fee. included in value.
49	fee: BalanceOf<T>,
50	/// The (total) amount that should be paid if the child-bounty is rewarded.
51	child_bounty_value: BalanceOf<T>,
52	/// The child-bounty curator fee. included in value.
53	child_bounty_fee: BalanceOf<T>,
54	/// Bounty description.
55	reason: Vec<u8>,
56}
57
58fn set_block_number<T: Config>(n: BlockNumberFor<T>) {
59	<T as pallet_treasury::Config>::BlockNumberProvider::set_block_number(n);
60}
61
62fn setup_bounty<T: Config>(
63	user: u32,
64	description: u32,
65) -> (T::AccountId, T::AccountId, BalanceOf<T>, BalanceOf<T>, Vec<u8>) {
66	let caller = account("caller", user, SEED);
67	let value: BalanceOf<T> = T::BountyValueMinimum::get().saturating_mul(100u32.into());
68	let fee = value / 2u32.into();
69	let deposit = T::BountyDepositBase::get() +
70		T::DataDepositPerByte::get() * T::MaximumReasonLength::get().into();
71	let _ = T::Currency::make_free_balance_be(&caller, deposit + T::Currency::minimum_balance());
72	let curator = account("curator", user, SEED);
73	let _ = T::Currency::make_free_balance_be(
74		&curator,
75		fee / 2u32.into() + T::Currency::minimum_balance(),
76	);
77	let reason = vec![0; description as usize];
78	(caller, curator, fee, value, reason)
79}
80
81fn setup_child_bounty<T: Config>(user: u32, description: u32) -> BenchmarkChildBounty<T> {
82	let (caller, curator, fee, value, reason) = setup_bounty::<T>(user, description);
83	let child_curator = account("child-curator", user, SEED);
84	let _ = T::Currency::make_free_balance_be(
85		&child_curator,
86		fee / 2u32.into() + T::Currency::minimum_balance(),
87	);
88	let child_bounty_value = (value - fee) / 4u32.into();
89	let child_bounty_fee = child_bounty_value / 2u32.into();
90
91	BenchmarkChildBounty::<T> {
92		bounty_id: 0,
93		child_bounty_id: 0,
94		caller,
95		curator,
96		child_curator,
97		value,
98		fee,
99		child_bounty_value,
100		child_bounty_fee,
101		reason,
102	}
103}
104
105fn activate_bounty<T: Config>(
106	user: u32,
107	description: u32,
108) -> Result<BenchmarkChildBounty<T>, BenchmarkError> {
109	let mut child_bounty_setup = setup_child_bounty::<T>(user, description);
110	let curator_lookup = T::Lookup::unlookup(child_bounty_setup.curator.clone());
111	Bounties::<T>::propose_bounty(
112		RawOrigin::Signed(child_bounty_setup.caller.clone()).into(),
113		child_bounty_setup.value,
114		child_bounty_setup.reason.clone(),
115	)?;
116
117	child_bounty_setup.bounty_id = pallet_bounties::BountyCount::<T>::get() - 1;
118
119	let approve_origin =
120		T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
121	Bounties::<T>::approve_bounty(approve_origin, child_bounty_setup.bounty_id)?;
122	set_block_number::<T>(T::SpendPeriod::get());
123	Treasury::<T>::on_initialize(frame_system::Pallet::<T>::block_number());
124	Bounties::<T>::propose_curator(
125		RawOrigin::Root.into(),
126		child_bounty_setup.bounty_id,
127		curator_lookup,
128		child_bounty_setup.fee,
129	)?;
130	Bounties::<T>::accept_curator(
131		RawOrigin::Signed(child_bounty_setup.curator.clone()).into(),
132		child_bounty_setup.bounty_id,
133	)?;
134
135	Ok(child_bounty_setup)
136}
137
138fn activate_child_bounty<T: Config>(
139	user: u32,
140	description: u32,
141) -> Result<BenchmarkChildBounty<T>, BenchmarkError> {
142	let mut bounty_setup = activate_bounty::<T>(user, description)?;
143	let child_curator_lookup = T::Lookup::unlookup(bounty_setup.child_curator.clone());
144
145	Pallet::<T>::add_child_bounty(
146		RawOrigin::Signed(bounty_setup.curator.clone()).into(),
147		bounty_setup.bounty_id,
148		bounty_setup.child_bounty_value,
149		bounty_setup.reason.clone(),
150	)?;
151
152	bounty_setup.child_bounty_id = ParentTotalChildBounties::<T>::get(bounty_setup.bounty_id) - 1;
153
154	Pallet::<T>::propose_curator(
155		RawOrigin::Signed(bounty_setup.curator.clone()).into(),
156		bounty_setup.bounty_id,
157		bounty_setup.child_bounty_id,
158		child_curator_lookup,
159		bounty_setup.child_bounty_fee,
160	)?;
161
162	Pallet::<T>::accept_curator(
163		RawOrigin::Signed(bounty_setup.child_curator.clone()).into(),
164		bounty_setup.bounty_id,
165		bounty_setup.child_bounty_id,
166	)?;
167
168	Ok(bounty_setup)
169}
170
171fn setup_pot_account<T: Config>() {
172	let pot_account = Bounties::<T>::account_id();
173	let value = T::Currency::minimum_balance().saturating_mul(1_000_000_000u32.into());
174	let _ = T::Currency::make_free_balance_be(&pot_account, value);
175}
176
177fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
178	frame_system::Pallet::<T>::assert_last_event(generic_event.into());
179}
180
181#[benchmarks]
182mod benchmarks {
183	use super::*;
184
185	#[benchmark]
186	fn add_child_bounty(
187		d: Linear<0, { T::MaximumReasonLength::get() }>,
188	) -> Result<(), BenchmarkError> {
189		setup_pot_account::<T>();
190		let bounty_setup = activate_bounty::<T>(0, d)?;
191
192		#[extrinsic_call]
193		_(
194			RawOrigin::Signed(bounty_setup.curator),
195			bounty_setup.bounty_id,
196			bounty_setup.child_bounty_value,
197			bounty_setup.reason.clone(),
198		);
199
200		assert_last_event::<T>(
201			Event::Added {
202				index: bounty_setup.bounty_id,
203				child_index: bounty_setup.child_bounty_id,
204			}
205			.into(),
206		);
207
208		Ok(())
209	}
210
211	#[benchmark]
212	fn propose_curator() -> Result<(), BenchmarkError> {
213		setup_pot_account::<T>();
214		let bounty_setup = activate_bounty::<T>(0, T::MaximumReasonLength::get())?;
215		let child_curator_lookup = T::Lookup::unlookup(bounty_setup.child_curator.clone());
216
217		Pallet::<T>::add_child_bounty(
218			RawOrigin::Signed(bounty_setup.curator.clone()).into(),
219			bounty_setup.bounty_id,
220			bounty_setup.child_bounty_value,
221			bounty_setup.reason.clone(),
222		)?;
223		let child_bounty_id = ParentTotalChildBounties::<T>::get(bounty_setup.bounty_id) - 1;
224
225		#[extrinsic_call]
226		_(
227			RawOrigin::Signed(bounty_setup.curator),
228			bounty_setup.bounty_id,
229			child_bounty_id,
230			child_curator_lookup,
231			bounty_setup.child_bounty_fee,
232		);
233
234		Ok(())
235	}
236
237	#[benchmark]
238	fn accept_curator() -> Result<(), BenchmarkError> {
239		setup_pot_account::<T>();
240		let mut bounty_setup = activate_bounty::<T>(0, T::MaximumReasonLength::get())?;
241		let child_curator_lookup = T::Lookup::unlookup(bounty_setup.child_curator.clone());
242
243		Pallet::<T>::add_child_bounty(
244			RawOrigin::Signed(bounty_setup.curator.clone()).into(),
245			bounty_setup.bounty_id,
246			bounty_setup.child_bounty_value,
247			bounty_setup.reason.clone(),
248		)?;
249		bounty_setup.child_bounty_id =
250			ParentTotalChildBounties::<T>::get(bounty_setup.bounty_id) - 1;
251
252		Pallet::<T>::propose_curator(
253			RawOrigin::Signed(bounty_setup.curator.clone()).into(),
254			bounty_setup.bounty_id,
255			bounty_setup.child_bounty_id,
256			child_curator_lookup,
257			bounty_setup.child_bounty_fee,
258		)?;
259
260		#[extrinsic_call]
261		_(
262			RawOrigin::Signed(bounty_setup.child_curator),
263			bounty_setup.bounty_id,
264			bounty_setup.child_bounty_id,
265		);
266
267		Ok(())
268	}
269
270	// Worst case when curator is inactive and any sender un-assigns the curator,
271	// or if `BountyUpdatePeriod` is large enough and `RejectOrigin` executes the call.
272	#[benchmark]
273	fn unassign_curator() -> Result<(), BenchmarkError> {
274		setup_pot_account::<T>();
275		let bounty_setup = activate_child_bounty::<T>(0, T::MaximumReasonLength::get())?;
276		Treasury::<T>::on_initialize(frame_system::Pallet::<T>::block_number());
277		let bounty_update_period = T::BountyUpdatePeriod::get();
278		let inactivity_timeout = T::SpendPeriod::get().saturating_add(bounty_update_period);
279		set_block_number::<T>(inactivity_timeout.saturating_add(1u32.into()));
280
281		// If `BountyUpdatePeriod` overflows the inactivity timeout the benchmark still
282		// executes the slash
283		let origin: T::RuntimeOrigin = if Pallet::<T>::treasury_block_number() <= inactivity_timeout
284		{
285			let child_curator = bounty_setup.child_curator;
286			T::RejectOrigin::try_successful_origin()
287				.unwrap_or_else(|_| RawOrigin::Signed(child_curator).into())
288		} else {
289			let caller = whitelisted_caller();
290			RawOrigin::Signed(caller).into()
291		};
292
293		#[extrinsic_call]
294		_(origin as T::RuntimeOrigin, bounty_setup.bounty_id, bounty_setup.child_bounty_id);
295
296		Ok(())
297	}
298
299	#[benchmark]
300	fn award_child_bounty() -> Result<(), BenchmarkError> {
301		setup_pot_account::<T>();
302		let bounty_setup = activate_child_bounty::<T>(0, T::MaximumReasonLength::get())?;
303		let beneficiary_account = account::<T::AccountId>("beneficiary", 0, SEED);
304		let beneficiary = T::Lookup::unlookup(beneficiary_account.clone());
305
306		#[extrinsic_call]
307		_(
308			RawOrigin::Signed(bounty_setup.child_curator),
309			bounty_setup.bounty_id,
310			bounty_setup.child_bounty_id,
311			beneficiary,
312		);
313
314		assert_last_event::<T>(
315			Event::Awarded {
316				index: bounty_setup.bounty_id,
317				child_index: bounty_setup.child_bounty_id,
318				beneficiary: beneficiary_account,
319			}
320			.into(),
321		);
322
323		Ok(())
324	}
325
326	#[benchmark]
327	fn claim_child_bounty() -> Result<(), BenchmarkError> {
328		setup_pot_account::<T>();
329		let bounty_setup = activate_child_bounty::<T>(0, T::MaximumReasonLength::get())?;
330		let beneficiary_account = account("beneficiary", 0, SEED);
331		let beneficiary = T::Lookup::unlookup(beneficiary_account);
332
333		Pallet::<T>::award_child_bounty(
334			RawOrigin::Signed(bounty_setup.child_curator.clone()).into(),
335			bounty_setup.bounty_id,
336			bounty_setup.child_bounty_id,
337			beneficiary,
338		)?;
339
340		let beneficiary_account = account("beneficiary", 0, SEED);
341
342		set_block_number::<T>(T::SpendPeriod::get() + T::BountyDepositPayoutDelay::get());
343		ensure!(
344			T::Currency::free_balance(&beneficiary_account).is_zero(),
345			"Beneficiary already has balance."
346		);
347
348		#[extrinsic_call]
349		_(
350			RawOrigin::Signed(bounty_setup.curator),
351			bounty_setup.bounty_id,
352			bounty_setup.child_bounty_id,
353		);
354
355		ensure!(
356			!T::Currency::free_balance(&beneficiary_account).is_zero(),
357			"Beneficiary didn't get paid."
358		);
359
360		Ok(())
361	}
362
363	// Best case scenario.
364	#[benchmark]
365	fn close_child_bounty_added() -> Result<(), BenchmarkError> {
366		setup_pot_account::<T>();
367		let mut bounty_setup = activate_bounty::<T>(0, T::MaximumReasonLength::get())?;
368
369		Pallet::<T>::add_child_bounty(
370			RawOrigin::Signed(bounty_setup.curator.clone()).into(),
371			bounty_setup.bounty_id,
372			bounty_setup.child_bounty_value,
373			bounty_setup.reason.clone(),
374		)?;
375		bounty_setup.child_bounty_id =
376			ParentTotalChildBounties::<T>::get(bounty_setup.bounty_id) - 1;
377
378		#[extrinsic_call]
379		close_child_bounty(RawOrigin::Root, bounty_setup.bounty_id, bounty_setup.child_bounty_id);
380
381		assert_last_event::<T>(
382			Event::Canceled {
383				index: bounty_setup.bounty_id,
384				child_index: bounty_setup.child_bounty_id,
385			}
386			.into(),
387		);
388
389		Ok(())
390	}
391
392	// Worst case scenario.
393	#[benchmark]
394	fn close_child_bounty_active() -> Result<(), BenchmarkError> {
395		setup_pot_account::<T>();
396		let bounty_setup = activate_child_bounty::<T>(0, T::MaximumReasonLength::get())?;
397		Treasury::<T>::on_initialize(frame_system::Pallet::<T>::block_number());
398
399		#[extrinsic_call]
400		close_child_bounty(RawOrigin::Root, bounty_setup.bounty_id, bounty_setup.child_bounty_id);
401
402		assert_last_event::<T>(
403			Event::Canceled {
404				index: bounty_setup.bounty_id,
405				child_index: bounty_setup.child_bounty_id,
406			}
407			.into(),
408		);
409
410		Ok(())
411	}
412
413	impl_benchmark_test_suite! {
414		Pallet,
415		tests::new_test_ext(),
416		tests::Test
417	}
418}