1use super::*;
21
22use alloc::{vec, vec::Vec};
23use frame_benchmarking::v2::*;
24use frame_system::{pallet_prelude::BlockNumberFor as SystemBlockNumberFor, RawOrigin};
25use sp_runtime::traits::{BlockNumberProvider, Bounded};
26
27use crate::Pallet as Bounties;
28use pallet_treasury::Pallet as Treasury;
29
30const SEED: u32 = 0;
31
32fn set_block_number<T: Config<I>, I: 'static>(n: BlockNumberFor<T, I>) {
33 <T as pallet_treasury::Config<I>>::BlockNumberProvider::set_block_number(n);
34}
35
36fn minimum_balance<T: Config<I>, I: 'static>() -> BalanceOf<T, I> {
37 let minimum_balance = T::Currency::minimum_balance();
38
39 if minimum_balance.is_zero() {
40 1u32.into()
41 } else {
42 minimum_balance
43 }
44}
45
46fn create_approved_bounties<T: Config<I>, I: 'static>(n: u32) -> Result<(), BenchmarkError> {
48 for i in 0..n {
49 let (caller, _curator, _fee, value, reason) =
50 setup_bounty::<T, I>(i, T::MaximumReasonLength::get());
51 Bounties::<T, I>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
52 let bounty_id = BountyCount::<T, I>::get() - 1;
53 let approve_origin =
54 T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
55 Bounties::<T, I>::approve_bounty(approve_origin, bounty_id)?;
56 }
57 ensure!(BountyApprovals::<T, I>::get().len() == n as usize, "Not all bounty approved");
58 Ok(())
59}
60
61fn setup_bounty<T: Config<I>, I: 'static>(
63 u: u32,
64 d: u32,
65) -> (T::AccountId, T::AccountId, BalanceOf<T, I>, BalanceOf<T, I>, Vec<u8>) {
66 let caller = account("caller", u, SEED);
67 let value: BalanceOf<T, I> = 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 + minimum_balance::<T, I>());
72 let curator = account("curator", u, SEED);
73 let _ =
74 T::Currency::make_free_balance_be(&curator, fee / 2u32.into() + minimum_balance::<T, I>());
75 let reason = vec![0; d as usize];
76 (caller, curator, fee, value, reason)
77}
78
79fn create_bounty<T: Config<I>, I: 'static>(
80) -> Result<(AccountIdLookupOf<T>, BountyIndex), BenchmarkError> {
81 let (caller, curator, fee, value, reason) =
82 setup_bounty::<T, I>(0, T::MaximumReasonLength::get());
83 let curator_lookup = T::Lookup::unlookup(curator.clone());
84 Bounties::<T, I>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
85 let bounty_id = BountyCount::<T, I>::get() - 1;
86 let approve_origin =
87 T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
88 Bounties::<T, I>::approve_bounty(approve_origin.clone(), bounty_id)?;
89 set_block_number::<T, I>(T::SpendPeriod::get());
90 Treasury::<T, I>::on_initialize(frame_system::Pallet::<T>::block_number());
91 Bounties::<T, I>::propose_curator(approve_origin, bounty_id, curator_lookup.clone(), fee)?;
92 Bounties::<T, I>::accept_curator(RawOrigin::Signed(curator).into(), bounty_id)?;
93 Ok((curator_lookup, bounty_id))
94}
95
96fn setup_pot_account<T: Config<I>, I: 'static>() {
97 let pot_account = Bounties::<T, I>::account_id();
98 let value = minimum_balance::<T, I>().saturating_mul(1_000_000_000u32.into());
99 let _ = T::Currency::make_free_balance_be(&pot_account, value);
100}
101
102fn assert_last_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::RuntimeEvent) {
103 frame_system::Pallet::<T>::assert_last_event(generic_event.into());
104}
105
106#[instance_benchmarks]
107mod benchmarks {
108 use super::*;
109
110 #[benchmark]
111 fn propose_bounty(d: Linear<0, { T::MaximumReasonLength::get() }>) {
112 let (caller, _, _, value, description) = setup_bounty::<T, I>(0, d);
113
114 #[extrinsic_call]
115 _(RawOrigin::Signed(caller), value, description);
116 }
117
118 #[benchmark]
119 fn approve_bounty() -> Result<(), BenchmarkError> {
120 let (caller, _, _, value, reason) = setup_bounty::<T, I>(0, T::MaximumReasonLength::get());
121 Bounties::<T, I>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
122 let bounty_id = BountyCount::<T, I>::get() - 1;
123 let approve_origin =
124 T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
125
126 #[extrinsic_call]
127 _(approve_origin as T::RuntimeOrigin, bounty_id);
128
129 Ok(())
130 }
131
132 #[benchmark]
133 fn propose_curator() -> Result<(), BenchmarkError> {
134 setup_pot_account::<T, I>();
135 let (caller, curator, fee, value, reason) =
136 setup_bounty::<T, I>(0, T::MaximumReasonLength::get());
137 let curator_lookup = T::Lookup::unlookup(curator);
138 Bounties::<T, I>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
139 let bounty_id = BountyCount::<T, I>::get() - 1;
140 let approve_origin =
141 T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
142 Bounties::<T, I>::approve_bounty(approve_origin.clone(), bounty_id)?;
143 set_block_number::<T, I>(T::SpendPeriod::get());
144 Treasury::<T, I>::on_initialize(frame_system::Pallet::<T>::block_number());
145
146 #[extrinsic_call]
147 _(approve_origin as T::RuntimeOrigin, bounty_id, curator_lookup, fee);
148
149 Ok(())
150 }
151
152 #[benchmark]
153 fn approve_bounty_with_curator() -> Result<(), BenchmarkError> {
154 setup_pot_account::<T, I>();
155 let (caller, curator, fee, value, reason) =
156 setup_bounty::<T, I>(0, T::MaximumReasonLength::get());
157 let curator_lookup = T::Lookup::unlookup(curator.clone());
158 Bounties::<T, I>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
159 let bounty_id = BountyCount::<T, I>::get() - 1;
160 let approve_origin =
161 T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
162 Treasury::<T, I>::on_initialize(SystemBlockNumberFor::<T>::zero());
163
164 #[extrinsic_call]
165 _(approve_origin as T::RuntimeOrigin, bounty_id, curator_lookup, fee);
166
167 assert_last_event::<T, I>(Event::CuratorProposed { bounty_id, curator }.into());
168
169 Ok(())
170 }
171
172 #[benchmark]
175 fn unassign_curator() -> Result<(), BenchmarkError> {
176 setup_pot_account::<T, I>();
177 let (curator_lookup, _) = create_bounty::<T, I>()?;
178 Treasury::<T, I>::on_initialize(frame_system::Pallet::<T>::block_number());
179 let bounty_id = BountyCount::<T, I>::get() - 1;
180 let bounty_update_period = T::BountyUpdatePeriod::get();
181 let inactivity_timeout = T::SpendPeriod::get().saturating_add(bounty_update_period);
182 set_block_number::<T, I>(inactivity_timeout.saturating_add(2u32.into()));
183
184 let origin = if Pallet::<T, I>::treasury_block_number() <= inactivity_timeout {
187 let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?;
188 T::RejectOrigin::try_successful_origin()
189 .unwrap_or_else(|_| RawOrigin::Signed(curator).into())
190 } else {
191 let caller = whitelisted_caller();
192 RawOrigin::Signed(caller).into()
193 };
194
195 #[extrinsic_call]
196 _(origin as T::RuntimeOrigin, bounty_id);
197
198 Ok(())
199 }
200
201 #[benchmark]
202 fn accept_curator() -> Result<(), BenchmarkError> {
203 setup_pot_account::<T, I>();
204 let (caller, curator, fee, value, reason) =
205 setup_bounty::<T, I>(0, T::MaximumReasonLength::get());
206 let curator_lookup = T::Lookup::unlookup(curator.clone());
207 Bounties::<T, I>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
208 let bounty_id = BountyCount::<T, I>::get() - 1;
209 let approve_origin =
210 T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
211 Bounties::<T, I>::approve_bounty(approve_origin.clone(), bounty_id)?;
212 set_block_number::<T, I>(T::SpendPeriod::get());
213 Treasury::<T, I>::on_initialize(frame_system::Pallet::<T>::block_number());
214 Bounties::<T, I>::propose_curator(approve_origin, bounty_id, curator_lookup, fee)?;
215
216 #[extrinsic_call]
217 _(RawOrigin::Signed(curator), bounty_id);
218
219 Ok(())
220 }
221
222 #[benchmark]
223 fn award_bounty() -> Result<(), BenchmarkError> {
224 setup_pot_account::<T, I>();
225 let (curator_lookup, _) = create_bounty::<T, I>()?;
226 Treasury::<T, I>::on_initialize(frame_system::Pallet::<T>::block_number());
227
228 let bounty_id = BountyCount::<T, I>::get() - 1;
229 let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?;
230
231 let beneficiary = T::Lookup::unlookup(account("beneficiary", 0, SEED));
232
233 #[extrinsic_call]
234 _(RawOrigin::Signed(curator), bounty_id, beneficiary);
235
236 Ok(())
237 }
238
239 #[benchmark]
240 fn claim_bounty() -> Result<(), BenchmarkError> {
241 setup_pot_account::<T, I>();
242 let (curator_lookup, _) = create_bounty::<T, I>()?;
243 Treasury::<T, I>::on_initialize(frame_system::Pallet::<T>::block_number());
244
245 let bounty_id = BountyCount::<T, I>::get() - 1;
246 let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?;
247
248 let beneficiary_account: T::AccountId = account("beneficiary", 0, SEED);
249 let beneficiary = T::Lookup::unlookup(beneficiary_account.clone());
250 Bounties::<T, I>::award_bounty(
251 RawOrigin::Signed(curator.clone()).into(),
252 bounty_id,
253 beneficiary,
254 )?;
255
256 set_block_number::<T, I>(
257 T::SpendPeriod::get() + T::BountyDepositPayoutDelay::get() + 1u32.into(),
258 );
259 assert!(
260 T::Currency::free_balance(&beneficiary_account).is_zero(),
261 "Beneficiary already has balance"
262 );
263
264 #[extrinsic_call]
265 _(RawOrigin::Signed(curator), bounty_id);
266
267 assert!(
268 !T::Currency::free_balance(&beneficiary_account).is_zero(),
269 "Beneficiary didn't get paid"
270 );
271
272 Ok(())
273 }
274
275 #[benchmark]
276 fn close_bounty_proposed() -> Result<(), BenchmarkError> {
277 setup_pot_account::<T, I>();
278 let (caller, _, _, value, reason) = setup_bounty::<T, I>(0, 0);
279 Bounties::<T, I>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
280 let bounty_id = BountyCount::<T, I>::get() - 1;
281 let approve_origin =
282 T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
283
284 #[extrinsic_call]
285 close_bounty(approve_origin, bounty_id);
286
287 Ok(())
288 }
289
290 #[benchmark]
291 fn close_bounty_active() -> Result<(), BenchmarkError> {
292 setup_pot_account::<T, I>();
293 let (_, _) = create_bounty::<T, I>()?;
294 Treasury::<T, I>::on_initialize(frame_system::Pallet::<T>::block_number());
295 let bounty_id = BountyCount::<T, I>::get() - 1;
296 let approve_origin =
297 T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
298
299 #[extrinsic_call]
300 close_bounty(approve_origin as T::RuntimeOrigin, bounty_id);
301
302 assert_last_event::<T, I>(Event::BountyCanceled { index: bounty_id }.into());
303
304 Ok(())
305 }
306
307 #[benchmark]
308 fn extend_bounty_expiry() -> Result<(), BenchmarkError> {
309 setup_pot_account::<T, I>();
310 let (curator_lookup, _) = create_bounty::<T, I>()?;
311 Treasury::<T, I>::on_initialize(frame_system::Pallet::<T>::block_number());
312
313 let bounty_id = BountyCount::<T, I>::get() - 1;
314 let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?;
315
316 #[extrinsic_call]
317 _(RawOrigin::Signed(curator), bounty_id, Vec::new());
318
319 assert_last_event::<T, I>(Event::BountyExtended { index: bounty_id }.into());
320
321 Ok(())
322 }
323
324 #[benchmark]
325 fn spend_funds(b: Linear<0, 100>) -> Result<(), BenchmarkError> {
326 setup_pot_account::<T, I>();
327 create_approved_bounties::<T, I>(b)?;
328
329 let mut budget_remaining = BalanceOf::<T, I>::max_value();
330 let mut imbalance = PositiveImbalanceOf::<T, I>::zero();
331 let mut total_weight = Weight::zero();
332 let mut missed_any = false;
333
334 #[block]
335 {
336 <Bounties<T, I> as pallet_treasury::SpendFunds<T, I>>::spend_funds(
337 &mut budget_remaining,
338 &mut imbalance,
339 &mut total_weight,
340 &mut missed_any,
341 );
342 }
343
344 assert!(!missed_any, "Missed some");
345
346 if b > 0 {
347 assert!(budget_remaining < BalanceOf::<T, I>::max_value(), "Budget not used");
348 assert_last_event::<T, I>(Event::BountyBecameActive { index: b - 1 }.into());
349 } else {
350 assert!(budget_remaining == BalanceOf::<T, I>::max_value(), "Budget used");
351 }
352
353 Ok(())
354 }
355
356 #[benchmark]
357 fn poke_deposit() -> Result<(), BenchmarkError> {
358 let (caller, _, _, value, reason) = setup_bounty::<T, I>(0, 5); Bounties::<T, I>::propose_bounty(RawOrigin::Signed(caller.clone()).into(), value, reason)?;
361 let bounty_id = BountyCount::<T, I>::get() - 1;
362 let old_deposit = T::Currency::reserved_balance(&caller);
363 let max_description: Vec<u8> = vec![0; T::MaximumReasonLength::get() as usize];
365 let bounded_description: BoundedVec<u8, T::MaximumReasonLength> =
366 max_description.try_into().unwrap();
367 BountyDescriptions::<T, I>::insert(bounty_id, &bounded_description);
368
369 let new_deposit = Bounties::<T, I>::calculate_bounty_deposit(&bounded_description);
371 let required_balance = new_deposit.saturating_add(minimum_balance::<T, I>());
372 T::Currency::make_free_balance_be(&caller, required_balance);
373
374 #[extrinsic_call]
375 _(RawOrigin::Signed(caller.clone()), bounty_id);
376
377 let bounty = crate::Bounties::<T, I>::get(bounty_id).unwrap();
378 assert_eq!(bounty.bond, new_deposit);
379 assert_eq!(T::Currency::reserved_balance(&caller), new_deposit);
380 assert_last_event::<T, I>(
381 Event::DepositPoked { bounty_id, proposer: caller, old_deposit, new_deposit }.into(),
382 );
383
384 Ok(())
385 }
386 impl_benchmark_test_suite!(
387 Bounties,
388 crate::tests::ExtBuilder::default().build(),
389 crate::tests::Test
390 );
391}