referrerpolicy=no-referrer-when-downgrade

pallet_multi_asset_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//! Bounties pallet benchmarking.
19
20#![cfg(feature = "runtime-benchmarks")]
21
22use super::*;
23use crate as pallet_bounties;
24use crate::Pallet as Bounties;
25
26use alloc::{borrow::Cow, vec};
27use frame_benchmarking::{v2::*, BenchmarkError};
28use frame_support::assert_ok;
29use frame_system::RawOrigin;
30use sp_core::crypto::FromEntropy;
31
32/// Trait describing factory functions for dispatchables' parameters.
33pub trait ArgumentsFactory<AssetKind, Beneficiary, Balance> {
34	/// Factory function for an asset kind.
35	fn create_asset_kind(seed: u32) -> AssetKind;
36
37	/// Factory function for a beneficiary.
38	fn create_beneficiary(seed: [u8; 32]) -> Beneficiary;
39}
40
41/// Implementation that expects the parameters implement the [`FromEntropy`] trait.
42impl<AssetKind, Beneficiary, Balance> ArgumentsFactory<AssetKind, Beneficiary, Balance> for ()
43where
44	AssetKind: FromEntropy,
45	Beneficiary: FromEntropy,
46{
47	fn create_asset_kind(seed: u32) -> AssetKind {
48		AssetKind::from_entropy(&mut seed.encode().as_slice()).unwrap()
49	}
50
51	fn create_beneficiary(seed: [u8; 32]) -> Beneficiary {
52		Beneficiary::from_entropy(&mut seed.as_slice()).unwrap()
53	}
54}
55
56#[derive(Clone)]
57struct BenchmarkBounty<T: Config<I>, I: 'static> {
58	/// Parent bounty ID.
59	parent_bounty_id: BountyIndex,
60	/// Child-bounty ID.
61	child_bounty_id: BountyIndex,
62	/// The parent bounty curator account.
63	curator: T::AccountId,
64	/// The child-bounty curator account.
65	child_curator: T::AccountId,
66	/// The kind of asset the child-/bounty is rewarded in.
67	asset_kind: T::AssetKind,
68	/// The amount that should be paid if the bounty is rewarded.
69	value: T::Balance,
70	/// The amount that should be paid if the child-bounty is rewarded.
71	child_value: T::Balance,
72	/// The child-/bounty beneficiary account.
73	beneficiary: T::Beneficiary,
74	/// Bounty metadata hash.
75	metadata: T::Hash,
76}
77
78const SEED: u32 = 0;
79
80fn assert_last_event<T: Config<I>, I: 'static>(
81	generic_event: <T as frame_system::Config>::RuntimeEvent,
82) {
83	frame_system::Pallet::<T>::assert_last_event(generic_event.into());
84}
85
86fn assert_has_event<T: Config<I>, I: 'static>(
87	generic_event: <T as frame_system::Config>::RuntimeEvent,
88) {
89	frame_system::Pallet::<T>::assert_has_event(generic_event.into());
90}
91
92pub fn get_payment_id<T: Config<I>, I: 'static>(
93	parent_bounty_id: BountyIndex,
94	child_bounty_id: Option<BountyIndex>,
95) -> Option<PaymentIdOf<T, I>> {
96	let bounty = Bounties::<T, I>::get_bounty_details(parent_bounty_id, child_bounty_id)
97		.expect("no bounty found");
98
99	match bounty.3 {
100		BountyStatus::FundingAttempted {
101			payment_status: PaymentState::Attempted { id }, ..
102		} => Some(id),
103		BountyStatus::RefundAttempted {
104			payment_status: PaymentState::Attempted { id }, ..
105		} => Some(id),
106		BountyStatus::PayoutAttempted {
107			payment_status: PaymentState::Attempted { id }, ..
108		} => Some(id),
109		_ => None,
110	}
111}
112
113// Create the pre-requisite information needed to `fund_bounty`.
114fn setup_bounty<T: Config<I>, I: 'static>() -> Result<BenchmarkBounty<T, I>, BenchmarkError> {
115	let asset_kind = <T as Config<I>>::BenchmarkHelper::create_asset_kind(SEED);
116	let min_native_value = T::BountyValueMinimum::get();
117	T::BalanceConverter::ensure_successful(asset_kind.clone());
118	let value = T::BalanceConverter::to_asset_balance(min_native_value, asset_kind.clone())
119		.map_err(|_| BenchmarkError::Stop("Failed to convert balance"))?;
120	let child_value = value / 2u32.into(); // so that retry works
121	let curator = account("curator", 0, SEED);
122	let child_curator = account("child-curator", 1, SEED);
123	let beneficiary =
124		<T as Config<I>>::BenchmarkHelper::create_beneficiary([(SEED).try_into().unwrap(); 32]);
125	let metadata = T::Preimages::note(Cow::from(vec![5, 6])).unwrap();
126
127	Ok(BenchmarkBounty::<T, I> {
128		parent_bounty_id: 0,
129		child_bounty_id: 0,
130		curator,
131		child_curator,
132		asset_kind,
133		value,
134		child_value,
135		beneficiary,
136		metadata,
137	})
138}
139
140fn create_parent_bounty<T: Config<I>, I: 'static>() -> Result<BenchmarkBounty<T, I>, BenchmarkError>
141{
142	let mut s = setup_bounty::<T, I>()?;
143
144	let spend_origin = T::SpendOrigin::try_successful_origin().map_err(|_| {
145		BenchmarkError::Stop("SpendOrigin has no successful origin required for the benchmark")
146	})?;
147	let funding_source_account =
148		Bounties::<T, I>::funding_source_account(s.asset_kind.clone()).expect("conversion failed");
149	let parent_bounty_account =
150		Bounties::<T, I>::bounty_account(s.parent_bounty_id, s.asset_kind.clone())
151			.expect("conversion failed");
152	let curator_lookup = T::Lookup::unlookup(s.curator.clone());
153	<T as pallet_bounties::Config<I>>::Paymaster::ensure_successful(
154		&funding_source_account,
155		&parent_bounty_account,
156		s.asset_kind.clone(),
157		s.value,
158	);
159
160	Bounties::<T, I>::fund_bounty(
161		spend_origin,
162		Box::new(s.asset_kind.clone()),
163		s.value,
164		curator_lookup,
165		s.metadata,
166	)?;
167
168	s.parent_bounty_id = pallet_bounties::BountyCount::<T, I>::get() - 1;
169
170	Ok(s)
171}
172
173fn create_funded_bounty<T: Config<I>, I: 'static>() -> Result<BenchmarkBounty<T, I>, BenchmarkError>
174{
175	let s = create_parent_bounty::<T, I>()?;
176
177	let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, None).expect("no payment attempt");
178	<T as pallet::Config<I>>::Paymaster::ensure_concluded(payment_id);
179
180	let caller = account("caller", 0, SEED);
181	Bounties::<T, I>::check_status(RawOrigin::Signed(caller).into(), s.parent_bounty_id, None)?;
182
183	Ok(s)
184}
185
186fn create_active_parent_bounty<T: Config<I>, I: 'static>(
187) -> Result<BenchmarkBounty<T, I>, BenchmarkError> {
188	let s = create_funded_bounty::<T, I>()?;
189	let curator = s.curator.clone();
190	<T as pallet_bounties::Config<I>>::Consideration::ensure_successful(&curator, s.value);
191
192	Bounties::<T, I>::accept_curator(RawOrigin::Signed(curator).into(), s.parent_bounty_id, None)?;
193
194	Ok(s)
195}
196
197fn create_child_bounty<T: Config<I>, I: 'static>() -> Result<BenchmarkBounty<T, I>, BenchmarkError>
198{
199	let mut s = create_active_parent_bounty::<T, I>()?;
200	let child_curator_lookup = T::Lookup::unlookup(s.child_curator.clone());
201
202	Bounties::<T, I>::fund_child_bounty(
203		RawOrigin::Signed(s.curator.clone()).into(),
204		s.parent_bounty_id,
205		s.child_value,
206		s.metadata,
207		Some(child_curator_lookup),
208	)?;
209	s.child_bounty_id =
210		pallet_bounties::TotalChildBountiesPerParent::<T, I>::get(s.parent_bounty_id) - 1;
211
212	Ok(s)
213}
214
215fn create_funded_child_bounty<T: Config<I>, I: 'static>(
216) -> Result<BenchmarkBounty<T, I>, BenchmarkError> {
217	let s = create_child_bounty::<T, I>()?;
218	let caller = account("caller", 0, SEED);
219
220	let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
221		.expect("no payment attempt");
222	<T as pallet::Config<I>>::Paymaster::ensure_concluded(payment_id);
223	Bounties::<T, I>::check_status(
224		RawOrigin::Signed(caller).into(),
225		s.parent_bounty_id,
226		Some(s.child_bounty_id),
227	)?;
228
229	Ok(s)
230}
231
232fn create_active_child_bounty<T: Config<I>, I: 'static>(
233) -> Result<BenchmarkBounty<T, I>, BenchmarkError> {
234	let s = create_funded_child_bounty::<T, I>()?;
235	let caller = s.child_curator.clone();
236	<T as pallet_bounties::Config<I>>::Consideration::ensure_successful(&caller, s.child_value);
237
238	Bounties::<T, I>::accept_curator(
239		RawOrigin::Signed(caller).into(),
240		s.parent_bounty_id,
241		Some(s.child_bounty_id),
242	)?;
243
244	Ok(s)
245}
246
247fn create_awarded_child_bounty<T: Config<I>, I: 'static>(
248) -> Result<BenchmarkBounty<T, I>, BenchmarkError> {
249	let s = create_active_child_bounty::<T, I>()?;
250	let caller = s.child_curator.clone();
251	let beneficiary_lookup = T::BeneficiaryLookup::unlookup(s.beneficiary.clone());
252
253	Bounties::<T, I>::award_bounty(
254		RawOrigin::Signed(caller).into(),
255		s.parent_bounty_id,
256		Some(s.child_bounty_id),
257		beneficiary_lookup,
258	)?;
259
260	Ok(s)
261}
262
263pub fn set_status<T: Config<I>, I: 'static>(
264	parent_bounty_id: BountyIndex,
265	child_bounty_id: Option<BountyIndex>,
266	new_payment_status: PaymentState<PaymentIdOf<T, I>>,
267) -> Result<(), BenchmarkError> {
268	let bounty =
269		pallet_bounties::Pallet::<T, I>::get_bounty_details(parent_bounty_id, child_bounty_id)
270			.expect("no bounty");
271
272	let new_status = match bounty.3 {
273		BountyStatus::FundingAttempted { curator, .. } =>
274			BountyStatus::FundingAttempted { payment_status: new_payment_status, curator },
275		BountyStatus::RefundAttempted { curator, .. } =>
276			BountyStatus::RefundAttempted { payment_status: new_payment_status, curator },
277		BountyStatus::PayoutAttempted { curator, beneficiary, .. } =>
278			BountyStatus::PayoutAttempted {
279				payment_status: new_payment_status,
280				curator,
281				beneficiary,
282			},
283		_ => return Err(BenchmarkError::Stop("unexpected bounty status")),
284	};
285
286	let _ = pallet_bounties::Pallet::<T, I>::update_bounty_status(
287		parent_bounty_id,
288		child_bounty_id,
289		new_status,
290	);
291
292	Ok(())
293}
294
295#[instance_benchmarks]
296mod benchmarks {
297	use super::*;
298
299	/// This benchmark is short-circuited if `SpendOrigin` cannot provide
300	/// a successful origin, in which case `fund_bounty` is un-callable and can use weight=0.
301	#[benchmark]
302	fn fund_bounty() -> Result<(), BenchmarkError> {
303		let s = setup_bounty::<T, I>()?;
304
305		let spend_origin =
306			T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
307		let curator_lookup = T::Lookup::unlookup(s.curator.clone());
308		let funding_source_account = Bounties::<T, I>::funding_source_account(s.asset_kind.clone())
309			.expect("conversion failed");
310		let parent_bounty_account =
311			Bounties::<T, I>::bounty_account(s.parent_bounty_id, s.asset_kind.clone())
312				.expect("conversion failed");
313		<T as pallet_bounties::Config<I>>::Paymaster::ensure_successful(
314			&funding_source_account,
315			&parent_bounty_account,
316			s.asset_kind.clone(),
317			s.value,
318		);
319
320		#[extrinsic_call]
321		_(spend_origin, Box::new(s.asset_kind), s.value, curator_lookup, s.metadata);
322
323		let parent_bounty_id = BountyCount::<T, I>::get() - 1;
324		assert_last_event::<T, I>(Event::BountyCreated { index: parent_bounty_id }.into());
325		let payment_id =
326			get_payment_id::<T, I>(parent_bounty_id, None).expect("no payment attempt");
327		assert_has_event::<T, I>(
328			Event::Paid { index: s.parent_bounty_id, child_index: None, payment_id }.into(),
329		);
330		assert_ne!(
331			<T as pallet_bounties::Config<I>>::Paymaster::check_payment(payment_id),
332			PaymentStatus::Failure
333		);
334
335		Ok(())
336	}
337
338	#[benchmark]
339	fn fund_child_bounty() -> Result<(), BenchmarkError> {
340		let s = create_active_parent_bounty::<T, I>()?;
341		let child_curator_lookup = T::Lookup::unlookup(s.child_curator.clone());
342
343		#[extrinsic_call]
344		_(
345			RawOrigin::Signed(s.curator),
346			s.parent_bounty_id,
347			s.child_value,
348			s.metadata,
349			Some(child_curator_lookup),
350		);
351
352		let child_bounty_id =
353			pallet_bounties::TotalChildBountiesPerParent::<T, I>::get(s.parent_bounty_id) - 1;
354		assert_last_event::<T, I>(
355			Event::ChildBountyCreated { index: s.parent_bounty_id, child_index: child_bounty_id }
356				.into(),
357		);
358		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(child_bounty_id))
359			.expect("no payment attempt");
360		assert_has_event::<T, I>(
361			Event::Paid {
362				index: s.parent_bounty_id,
363				child_index: Some(child_bounty_id),
364				payment_id,
365			}
366			.into(),
367		);
368		assert_ne!(
369			<T as pallet_bounties::Config<I>>::Paymaster::check_payment(payment_id),
370			PaymentStatus::Failure
371		);
372
373		Ok(())
374	}
375
376	/// This benchmark is short-circuited if `SpendOrigin` cannot provide
377	/// a successful origin, in which case `propose_curator` is un-callable and can use weight=0.
378	#[benchmark]
379	fn propose_curator_parent_bounty() -> Result<(), BenchmarkError> {
380		let s = create_funded_bounty::<T, I>()?;
381
382		let spend_origin =
383			T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
384		Bounties::<T, I>::unassign_curator(
385			RawOrigin::Signed(s.curator.clone()).into(),
386			s.parent_bounty_id,
387			None,
388		)?;
389		let curator_lookup = T::Lookup::unlookup(s.curator.clone());
390
391		#[block]
392		{
393			assert_ok!(Bounties::<T, I>::propose_curator(
394				spend_origin,
395				s.parent_bounty_id,
396				None,
397				curator_lookup,
398			));
399		}
400
401		assert_last_event::<T, I>(
402			Event::CuratorProposed {
403				index: s.parent_bounty_id,
404				child_index: None,
405				curator: s.curator,
406			}
407			.into(),
408		);
409
410		Ok(())
411	}
412
413	#[benchmark]
414	fn propose_curator_child_bounty() -> Result<(), BenchmarkError> {
415		let s = create_funded_child_bounty::<T, I>()?;
416		let child_curator_lookup = T::Lookup::unlookup(s.child_curator.clone());
417
418		Bounties::<T, I>::unassign_curator(
419			RawOrigin::Signed(s.curator.clone()).into(),
420			s.parent_bounty_id,
421			Some(s.child_bounty_id),
422		)?;
423
424		#[block]
425		{
426			assert_ok!(Bounties::<T, I>::propose_curator(
427				RawOrigin::Signed(s.curator).into(),
428				s.parent_bounty_id,
429				Some(s.child_bounty_id),
430				child_curator_lookup,
431			));
432		}
433
434		assert_last_event::<T, I>(
435			Event::CuratorProposed {
436				index: s.parent_bounty_id,
437				child_index: Some(s.child_bounty_id),
438				curator: s.child_curator,
439			}
440			.into(),
441		);
442
443		Ok(())
444	}
445
446	#[benchmark]
447	fn accept_curator() -> Result<(), BenchmarkError> {
448		let s = create_funded_child_bounty::<T, I>()?;
449		let caller = s.child_curator.clone();
450
451		<T as pallet_bounties::Config<I>>::Consideration::ensure_successful(&caller, s.child_value);
452
453		#[block]
454		{
455			assert_ok!(Bounties::<T, I>::accept_curator(
456				RawOrigin::Signed(caller).into(),
457				s.parent_bounty_id,
458				Some(s.child_bounty_id),
459			));
460		}
461
462		assert_last_event::<T, I>(
463			Event::BountyBecameActive {
464				index: s.parent_bounty_id,
465				child_index: Some(s.child_bounty_id),
466				curator: s.child_curator,
467			}
468			.into(),
469		);
470
471		Ok(())
472	}
473
474	#[benchmark]
475	fn unassign_curator() -> Result<(), BenchmarkError> {
476		let s = create_active_child_bounty::<T, I>()?;
477
478		#[extrinsic_call]
479		_(RawOrigin::Signed(s.curator), s.parent_bounty_id, Some(s.child_bounty_id));
480
481		assert_last_event::<T, I>(
482			Event::CuratorUnassigned {
483				index: s.parent_bounty_id,
484				child_index: Some(s.child_bounty_id),
485			}
486			.into(),
487		);
488
489		Ok(())
490	}
491
492	#[benchmark]
493	fn award_bounty() -> Result<(), BenchmarkError> {
494		let s = create_active_child_bounty::<T, I>()?;
495		let beneficiary_lookup = T::BeneficiaryLookup::unlookup(s.beneficiary.clone());
496
497		#[extrinsic_call]
498		_(
499			RawOrigin::Signed(s.child_curator),
500			s.parent_bounty_id,
501			Some(s.child_bounty_id),
502			beneficiary_lookup,
503		);
504
505		assert_last_event::<T, I>(
506			Event::BountyAwarded {
507				index: s.parent_bounty_id,
508				child_index: Some(s.child_bounty_id),
509				beneficiary: s.beneficiary,
510			}
511			.into(),
512		);
513
514		Ok(())
515	}
516
517	#[benchmark]
518	fn close_parent_bounty() -> Result<(), BenchmarkError> {
519		let s = create_active_parent_bounty::<T, I>()?;
520
521		let reject_origin =
522			T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
523
524		#[block]
525		{
526			assert_ok!(Bounties::<T, I>::close_bounty(
527				reject_origin.clone(),
528				s.parent_bounty_id,
529				None
530			));
531		}
532
533		assert_last_event::<T, I>(
534			Event::BountyCanceled { index: s.parent_bounty_id, child_index: None }.into(),
535		);
536		let payment_id =
537			get_payment_id::<T, I>(s.parent_bounty_id, None).expect("no payment attempt");
538		assert_has_event::<T, I>(
539			Event::Paid { index: s.parent_bounty_id, child_index: None, payment_id }.into(),
540		);
541		assert_ne!(
542			<T as pallet_bounties::Config<I>>::Paymaster::check_payment(payment_id),
543			PaymentStatus::Failure
544		);
545		assert!(Bounties::<T, I>::close_bounty(reject_origin, s.parent_bounty_id, None).is_err());
546
547		Ok(())
548	}
549
550	#[benchmark]
551	fn close_child_bounty() -> Result<(), BenchmarkError> {
552		let s = create_active_child_bounty::<T, I>()?;
553
554		let reject_origin =
555			T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
556
557		#[block]
558		{
559			assert_ok!(Bounties::<T, I>::close_bounty(
560				reject_origin.clone(),
561				s.parent_bounty_id,
562				Some(s.child_bounty_id),
563			));
564		}
565
566		assert_last_event::<T, I>(
567			Event::BountyCanceled {
568				index: s.parent_bounty_id,
569				child_index: Some(s.child_bounty_id),
570			}
571			.into(),
572		);
573		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
574			.expect("no payment attempt");
575		assert_has_event::<T, I>(
576			Event::Paid {
577				index: s.parent_bounty_id,
578				child_index: Some(s.child_bounty_id),
579				payment_id,
580			}
581			.into(),
582		);
583		assert_ne!(
584			<T as pallet_bounties::Config<I>>::Paymaster::check_payment(payment_id),
585			PaymentStatus::Failure
586		);
587		assert!(Bounties::<T, I>::close_bounty(
588			reject_origin,
589			s.parent_bounty_id,
590			Some(s.child_bounty_id)
591		)
592		.is_err());
593
594		Ok(())
595	}
596
597	#[benchmark]
598	fn check_status_funding() -> Result<(), BenchmarkError> {
599		let s = create_child_bounty::<T, I>()?;
600		let caller = s.curator.clone();
601
602		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
603			.expect("no payment attempt");
604		<T as pallet_bounties::Config<I>>::Paymaster::ensure_concluded(payment_id);
605
606		#[block]
607		{
608			assert_ok!(Bounties::<T, I>::check_status(
609				RawOrigin::Signed(caller).into(),
610				s.parent_bounty_id,
611				Some(s.child_bounty_id),
612			));
613		}
614
615		assert_last_event::<T, I>(
616			Event::BountyFundingProcessed {
617				index: s.parent_bounty_id,
618				child_index: Some(s.child_bounty_id),
619			}
620			.into(),
621		);
622		let child_bounty =
623			pallet_bounties::ChildBounties::<T, I>::get(s.parent_bounty_id, s.child_bounty_id)
624				.expect("no bounty");
625		assert!(matches!(child_bounty.status, BountyStatus::Funded { .. }));
626
627		Ok(())
628	}
629
630	#[benchmark]
631	fn check_status_refund() -> Result<(), BenchmarkError> {
632		let s = create_active_child_bounty::<T, I>()?;
633		let caller = s.curator.clone();
634
635		Bounties::<T, I>::close_bounty(
636			RawOrigin::Signed(caller.clone()).into(),
637			s.parent_bounty_id,
638			Some(s.child_bounty_id),
639		)?;
640		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
641			.expect("no payment attempt");
642		<T as pallet_bounties::Config<I>>::Paymaster::ensure_concluded(payment_id);
643
644		#[block]
645		{
646			assert_ok!(Bounties::<T, I>::check_status(
647				RawOrigin::Signed(caller).into(),
648				s.parent_bounty_id,
649				Some(s.child_bounty_id),
650			));
651		}
652
653		assert_has_event::<T, I>(
654			Event::BountyRefundProcessed {
655				index: s.parent_bounty_id,
656				child_index: Some(s.child_bounty_id),
657			}
658			.into(),
659		);
660		assert_eq!(
661			pallet_bounties::ChildBounties::<T, I>::get(s.parent_bounty_id, s.child_bounty_id),
662			None
663		);
664
665		Ok(())
666	}
667
668	#[benchmark]
669	fn check_status_payout() -> Result<(), BenchmarkError> {
670		let s = create_awarded_child_bounty::<T, I>()?;
671		let caller = s.child_curator.clone();
672
673		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
674			.expect("no payment attempt");
675		<T as pallet_bounties::Config<I>>::Paymaster::ensure_concluded(payment_id);
676
677		#[block]
678		{
679			assert_ok!(Bounties::<T, I>::check_status(
680				RawOrigin::Signed(caller).into(),
681				s.parent_bounty_id,
682				Some(s.child_bounty_id),
683			));
684		}
685
686		assert_has_event::<T, I>(
687			Event::BountyPayoutProcessed {
688				index: s.parent_bounty_id,
689				child_index: Some(s.child_bounty_id),
690				asset_kind: s.asset_kind,
691				value: s.child_value,
692				beneficiary: s.beneficiary,
693			}
694			.into(),
695		);
696		assert_eq!(
697			pallet_bounties::ChildBounties::<T, I>::get(s.parent_bounty_id, s.child_bounty_id),
698			None
699		);
700
701		Ok(())
702	}
703
704	#[benchmark]
705	fn retry_payment_funding() -> Result<(), BenchmarkError> {
706		let s = create_child_bounty::<T, I>()?;
707		let caller = s.curator.clone();
708
709		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
710			.expect("no payment attempt");
711		<T as pallet_bounties::Config<I>>::Paymaster::ensure_concluded(payment_id);
712		set_status::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id), PaymentState::Failed)?;
713
714		#[block]
715		{
716			assert_ok!(Bounties::<T, I>::retry_payment(
717				RawOrigin::Signed(caller.clone()).into(),
718				s.parent_bounty_id,
719				Some(s.child_bounty_id),
720			));
721		}
722
723		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
724			.expect("no payment attempt");
725		assert_last_event::<T, I>(
726			Event::Paid {
727				index: s.parent_bounty_id,
728				child_index: Some(s.child_bounty_id),
729				payment_id,
730			}
731			.into(),
732		);
733		assert_ne!(
734			<T as pallet::Config<I>>::Paymaster::check_payment(payment_id),
735			PaymentStatus::Failure
736		);
737		assert!(Bounties::<T, I>::retry_payment(
738			RawOrigin::Signed(caller).into(),
739			s.parent_bounty_id,
740			Some(s.child_bounty_id),
741		)
742		.is_err());
743
744		Ok(())
745	}
746
747	#[benchmark]
748	fn retry_payment_refund() -> Result<(), BenchmarkError> {
749		let s = create_active_child_bounty::<T, I>()?;
750		let caller = s.curator.clone();
751
752		let new_status = BountyStatus::RefundAttempted {
753			payment_status: PaymentState::Failed,
754			curator: Some(s.child_curator),
755		};
756		let _ = pallet_bounties::Pallet::<T, I>::update_bounty_status(
757			s.parent_bounty_id,
758			Some(s.child_bounty_id),
759			new_status,
760		);
761
762		#[block]
763		{
764			assert_ok!(Bounties::<T, I>::retry_payment(
765				RawOrigin::Signed(caller.clone()).into(),
766				s.parent_bounty_id,
767				Some(s.child_bounty_id),
768			));
769		}
770
771		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
772			.expect("no payment attempt");
773		assert_last_event::<T, I>(
774			Event::Paid {
775				index: s.parent_bounty_id,
776				child_index: Some(s.child_bounty_id),
777				payment_id,
778			}
779			.into(),
780		);
781		assert_ne!(
782			<T as pallet::Config<I>>::Paymaster::check_payment(payment_id),
783			PaymentStatus::Failure
784		);
785		assert!(Bounties::<T, I>::retry_payment(
786			RawOrigin::Signed(caller).into(),
787			s.parent_bounty_id,
788			Some(s.child_bounty_id),
789		)
790		.is_err());
791
792		Ok(())
793	}
794
795	#[benchmark]
796	fn retry_payment_payout() -> Result<(), BenchmarkError> {
797		let s = create_active_child_bounty::<T, I>()?;
798		let caller = s.curator.clone();
799
800		let new_status = BountyStatus::PayoutAttempted {
801			payment_status: PaymentState::Failed,
802			curator: s.child_curator.clone(),
803			beneficiary: s.beneficiary.clone(),
804		};
805		let _ = pallet_bounties::Pallet::<T, I>::update_bounty_status(
806			s.parent_bounty_id,
807			Some(s.child_bounty_id),
808			new_status,
809		);
810
811		#[block]
812		{
813			assert_ok!(Bounties::<T, I>::retry_payment(
814				RawOrigin::Signed(caller.clone()).into(),
815				s.parent_bounty_id,
816				Some(s.child_bounty_id),
817			));
818		}
819
820		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
821			.expect("no payment attempt");
822		assert_last_event::<T, I>(
823			Event::Paid {
824				index: s.parent_bounty_id,
825				child_index: Some(s.child_bounty_id),
826				payment_id,
827			}
828			.into(),
829		);
830		assert_ne!(
831			<T as pallet::Config<I>>::Paymaster::check_payment(payment_id),
832			PaymentStatus::Failure
833		);
834		assert!(Bounties::<T, I>::retry_payment(
835			RawOrigin::Signed(caller).into(),
836			s.parent_bounty_id,
837			Some(s.child_bounty_id),
838		)
839		.is_err());
840
841		Ok(())
842	}
843
844	impl_benchmark_test_suite! {
845		Pallet,
846		crate::mock::ExtBuilder::default().build(),
847		crate::mock::Test
848	}
849}