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		},
276		BountyStatus::RefundAttempted { curator, .. } => {
277			BountyStatus::RefundAttempted { payment_status: new_payment_status, curator }
278		},
279		BountyStatus::PayoutAttempted { curator, beneficiary, .. } => {
280			BountyStatus::PayoutAttempted {
281				payment_status: new_payment_status,
282				curator,
283				beneficiary,
284			}
285		},
286		_ => return Err(BenchmarkError::Stop("unexpected bounty status")),
287	};
288
289	let _ = pallet_bounties::Pallet::<T, I>::update_bounty_status(
290		parent_bounty_id,
291		child_bounty_id,
292		new_status,
293	);
294
295	Ok(())
296}
297
298#[instance_benchmarks]
299mod benchmarks {
300	use super::*;
301
302	/// This benchmark is short-circuited if `SpendOrigin` cannot provide
303	/// a successful origin, in which case `fund_bounty` is un-callable and can use weight=0.
304	#[benchmark]
305	fn fund_bounty() -> Result<(), BenchmarkError> {
306		let s = setup_bounty::<T, I>()?;
307
308		let spend_origin =
309			T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
310		let curator_lookup = T::Lookup::unlookup(s.curator.clone());
311		let funding_source_account = Bounties::<T, I>::funding_source_account(s.asset_kind.clone())
312			.expect("conversion failed");
313		let parent_bounty_account =
314			Bounties::<T, I>::bounty_account(s.parent_bounty_id, s.asset_kind.clone())
315				.expect("conversion failed");
316		<T as pallet_bounties::Config<I>>::Paymaster::ensure_successful(
317			&funding_source_account,
318			&parent_bounty_account,
319			s.asset_kind.clone(),
320			s.value,
321		);
322
323		#[extrinsic_call]
324		_(spend_origin, Box::new(s.asset_kind), s.value, curator_lookup, s.metadata);
325
326		let parent_bounty_id = BountyCount::<T, I>::get() - 1;
327		assert_last_event::<T, I>(Event::BountyCreated { index: parent_bounty_id }.into());
328		let payment_id =
329			get_payment_id::<T, I>(parent_bounty_id, None).expect("no payment attempt");
330		assert_has_event::<T, I>(
331			Event::Paid { index: s.parent_bounty_id, child_index: None, payment_id }.into(),
332		);
333		assert_ne!(
334			<T as pallet_bounties::Config<I>>::Paymaster::check_payment(payment_id),
335			PaymentStatus::Failure
336		);
337
338		Ok(())
339	}
340
341	#[benchmark]
342	fn fund_child_bounty() -> Result<(), BenchmarkError> {
343		let s = create_active_parent_bounty::<T, I>()?;
344		let child_curator_lookup = T::Lookup::unlookup(s.child_curator.clone());
345
346		#[extrinsic_call]
347		_(
348			RawOrigin::Signed(s.curator),
349			s.parent_bounty_id,
350			s.child_value,
351			s.metadata,
352			Some(child_curator_lookup),
353		);
354
355		let child_bounty_id =
356			pallet_bounties::TotalChildBountiesPerParent::<T, I>::get(s.parent_bounty_id) - 1;
357		assert_last_event::<T, I>(
358			Event::ChildBountyCreated { index: s.parent_bounty_id, child_index: child_bounty_id }
359				.into(),
360		);
361		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(child_bounty_id))
362			.expect("no payment attempt");
363		assert_has_event::<T, I>(
364			Event::Paid {
365				index: s.parent_bounty_id,
366				child_index: Some(child_bounty_id),
367				payment_id,
368			}
369			.into(),
370		);
371		assert_ne!(
372			<T as pallet_bounties::Config<I>>::Paymaster::check_payment(payment_id),
373			PaymentStatus::Failure
374		);
375
376		Ok(())
377	}
378
379	/// This benchmark is short-circuited if `SpendOrigin` cannot provide
380	/// a successful origin, in which case `propose_curator` is un-callable and can use weight=0.
381	#[benchmark]
382	fn propose_curator_parent_bounty() -> Result<(), BenchmarkError> {
383		let s = create_funded_bounty::<T, I>()?;
384
385		let spend_origin =
386			T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
387		Bounties::<T, I>::unassign_curator(
388			RawOrigin::Signed(s.curator.clone()).into(),
389			s.parent_bounty_id,
390			None,
391		)?;
392		let curator_lookup = T::Lookup::unlookup(s.curator.clone());
393
394		#[block]
395		{
396			assert_ok!(Bounties::<T, I>::propose_curator(
397				spend_origin,
398				s.parent_bounty_id,
399				None,
400				curator_lookup,
401			));
402		}
403
404		assert_last_event::<T, I>(
405			Event::CuratorProposed {
406				index: s.parent_bounty_id,
407				child_index: None,
408				curator: s.curator,
409			}
410			.into(),
411		);
412
413		Ok(())
414	}
415
416	#[benchmark]
417	fn propose_curator_child_bounty() -> Result<(), BenchmarkError> {
418		let s = create_funded_child_bounty::<T, I>()?;
419		let child_curator_lookup = T::Lookup::unlookup(s.child_curator.clone());
420
421		Bounties::<T, I>::unassign_curator(
422			RawOrigin::Signed(s.curator.clone()).into(),
423			s.parent_bounty_id,
424			Some(s.child_bounty_id),
425		)?;
426
427		#[block]
428		{
429			assert_ok!(Bounties::<T, I>::propose_curator(
430				RawOrigin::Signed(s.curator).into(),
431				s.parent_bounty_id,
432				Some(s.child_bounty_id),
433				child_curator_lookup,
434			));
435		}
436
437		assert_last_event::<T, I>(
438			Event::CuratorProposed {
439				index: s.parent_bounty_id,
440				child_index: Some(s.child_bounty_id),
441				curator: s.child_curator,
442			}
443			.into(),
444		);
445
446		Ok(())
447	}
448
449	#[benchmark]
450	fn accept_curator() -> Result<(), BenchmarkError> {
451		let s = create_funded_child_bounty::<T, I>()?;
452		let caller = s.child_curator.clone();
453
454		<T as pallet_bounties::Config<I>>::Consideration::ensure_successful(&caller, s.child_value);
455
456		#[block]
457		{
458			assert_ok!(Bounties::<T, I>::accept_curator(
459				RawOrigin::Signed(caller).into(),
460				s.parent_bounty_id,
461				Some(s.child_bounty_id),
462			));
463		}
464
465		assert_last_event::<T, I>(
466			Event::BountyBecameActive {
467				index: s.parent_bounty_id,
468				child_index: Some(s.child_bounty_id),
469				curator: s.child_curator,
470			}
471			.into(),
472		);
473
474		Ok(())
475	}
476
477	#[benchmark]
478	fn unassign_curator() -> Result<(), BenchmarkError> {
479		let s = create_active_child_bounty::<T, I>()?;
480
481		#[extrinsic_call]
482		_(RawOrigin::Signed(s.curator), s.parent_bounty_id, Some(s.child_bounty_id));
483
484		assert_last_event::<T, I>(
485			Event::CuratorUnassigned {
486				index: s.parent_bounty_id,
487				child_index: Some(s.child_bounty_id),
488			}
489			.into(),
490		);
491
492		Ok(())
493	}
494
495	#[benchmark]
496	fn award_bounty() -> Result<(), BenchmarkError> {
497		let s = create_active_child_bounty::<T, I>()?;
498		let beneficiary_lookup = T::BeneficiaryLookup::unlookup(s.beneficiary.clone());
499
500		#[extrinsic_call]
501		_(
502			RawOrigin::Signed(s.child_curator),
503			s.parent_bounty_id,
504			Some(s.child_bounty_id),
505			beneficiary_lookup,
506		);
507
508		assert_last_event::<T, I>(
509			Event::BountyAwarded {
510				index: s.parent_bounty_id,
511				child_index: Some(s.child_bounty_id),
512				beneficiary: s.beneficiary,
513			}
514			.into(),
515		);
516
517		Ok(())
518	}
519
520	#[benchmark]
521	fn close_parent_bounty() -> Result<(), BenchmarkError> {
522		let s = create_active_parent_bounty::<T, I>()?;
523
524		let reject_origin =
525			T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
526
527		#[block]
528		{
529			assert_ok!(Bounties::<T, I>::close_bounty(
530				reject_origin.clone(),
531				s.parent_bounty_id,
532				None
533			));
534		}
535
536		assert_last_event::<T, I>(
537			Event::BountyCanceled { index: s.parent_bounty_id, child_index: None }.into(),
538		);
539		let payment_id =
540			get_payment_id::<T, I>(s.parent_bounty_id, None).expect("no payment attempt");
541		assert_has_event::<T, I>(
542			Event::Paid { index: s.parent_bounty_id, child_index: None, payment_id }.into(),
543		);
544		assert_ne!(
545			<T as pallet_bounties::Config<I>>::Paymaster::check_payment(payment_id),
546			PaymentStatus::Failure
547		);
548		assert!(Bounties::<T, I>::close_bounty(reject_origin, s.parent_bounty_id, None).is_err());
549
550		Ok(())
551	}
552
553	#[benchmark]
554	fn close_child_bounty() -> Result<(), BenchmarkError> {
555		let s = create_active_child_bounty::<T, I>()?;
556
557		let reject_origin =
558			T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
559
560		#[block]
561		{
562			assert_ok!(Bounties::<T, I>::close_bounty(
563				reject_origin.clone(),
564				s.parent_bounty_id,
565				Some(s.child_bounty_id),
566			));
567		}
568
569		assert_last_event::<T, I>(
570			Event::BountyCanceled {
571				index: s.parent_bounty_id,
572				child_index: Some(s.child_bounty_id),
573			}
574			.into(),
575		);
576		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
577			.expect("no payment attempt");
578		assert_has_event::<T, I>(
579			Event::Paid {
580				index: s.parent_bounty_id,
581				child_index: Some(s.child_bounty_id),
582				payment_id,
583			}
584			.into(),
585		);
586		assert_ne!(
587			<T as pallet_bounties::Config<I>>::Paymaster::check_payment(payment_id),
588			PaymentStatus::Failure
589		);
590		assert!(Bounties::<T, I>::close_bounty(
591			reject_origin,
592			s.parent_bounty_id,
593			Some(s.child_bounty_id)
594		)
595		.is_err());
596
597		Ok(())
598	}
599
600	#[benchmark]
601	fn check_status_funding() -> Result<(), BenchmarkError> {
602		let s = create_child_bounty::<T, I>()?;
603		let caller = s.curator.clone();
604
605		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
606			.expect("no payment attempt");
607		<T as pallet_bounties::Config<I>>::Paymaster::ensure_concluded(payment_id);
608
609		#[block]
610		{
611			assert_ok!(Bounties::<T, I>::check_status(
612				RawOrigin::Signed(caller).into(),
613				s.parent_bounty_id,
614				Some(s.child_bounty_id),
615			));
616		}
617
618		assert_last_event::<T, I>(
619			Event::BountyFundingProcessed {
620				index: s.parent_bounty_id,
621				child_index: Some(s.child_bounty_id),
622			}
623			.into(),
624		);
625		let child_bounty =
626			pallet_bounties::ChildBounties::<T, I>::get(s.parent_bounty_id, s.child_bounty_id)
627				.expect("no bounty");
628		assert!(matches!(child_bounty.status, BountyStatus::Funded { .. }));
629
630		Ok(())
631	}
632
633	#[benchmark]
634	fn check_status_refund() -> Result<(), BenchmarkError> {
635		let s = create_active_child_bounty::<T, I>()?;
636		let caller = s.curator.clone();
637
638		Bounties::<T, I>::close_bounty(
639			RawOrigin::Signed(caller.clone()).into(),
640			s.parent_bounty_id,
641			Some(s.child_bounty_id),
642		)?;
643		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
644			.expect("no payment attempt");
645		<T as pallet_bounties::Config<I>>::Paymaster::ensure_concluded(payment_id);
646
647		#[block]
648		{
649			assert_ok!(Bounties::<T, I>::check_status(
650				RawOrigin::Signed(caller).into(),
651				s.parent_bounty_id,
652				Some(s.child_bounty_id),
653			));
654		}
655
656		assert_has_event::<T, I>(
657			Event::BountyRefundProcessed {
658				index: s.parent_bounty_id,
659				child_index: Some(s.child_bounty_id),
660			}
661			.into(),
662		);
663		assert_eq!(
664			pallet_bounties::ChildBounties::<T, I>::get(s.parent_bounty_id, s.child_bounty_id),
665			None
666		);
667
668		Ok(())
669	}
670
671	#[benchmark]
672	fn check_status_payout() -> Result<(), BenchmarkError> {
673		let s = create_awarded_child_bounty::<T, I>()?;
674		let caller = s.child_curator.clone();
675
676		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
677			.expect("no payment attempt");
678		<T as pallet_bounties::Config<I>>::Paymaster::ensure_concluded(payment_id);
679
680		#[block]
681		{
682			assert_ok!(Bounties::<T, I>::check_status(
683				RawOrigin::Signed(caller).into(),
684				s.parent_bounty_id,
685				Some(s.child_bounty_id),
686			));
687		}
688
689		assert_has_event::<T, I>(
690			Event::BountyPayoutProcessed {
691				index: s.parent_bounty_id,
692				child_index: Some(s.child_bounty_id),
693				asset_kind: s.asset_kind,
694				value: s.child_value,
695				beneficiary: s.beneficiary,
696			}
697			.into(),
698		);
699		assert_eq!(
700			pallet_bounties::ChildBounties::<T, I>::get(s.parent_bounty_id, s.child_bounty_id),
701			None
702		);
703
704		Ok(())
705	}
706
707	#[benchmark]
708	fn retry_payment_funding() -> Result<(), BenchmarkError> {
709		let s = create_child_bounty::<T, I>()?;
710		let caller = s.curator.clone();
711
712		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
713			.expect("no payment attempt");
714		<T as pallet_bounties::Config<I>>::Paymaster::ensure_concluded(payment_id);
715		set_status::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id), PaymentState::Failed)?;
716
717		#[block]
718		{
719			assert_ok!(Bounties::<T, I>::retry_payment(
720				RawOrigin::Signed(caller.clone()).into(),
721				s.parent_bounty_id,
722				Some(s.child_bounty_id),
723			));
724		}
725
726		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
727			.expect("no payment attempt");
728		assert_last_event::<T, I>(
729			Event::Paid {
730				index: s.parent_bounty_id,
731				child_index: Some(s.child_bounty_id),
732				payment_id,
733			}
734			.into(),
735		);
736		assert_ne!(
737			<T as pallet::Config<I>>::Paymaster::check_payment(payment_id),
738			PaymentStatus::Failure
739		);
740		assert!(Bounties::<T, I>::retry_payment(
741			RawOrigin::Signed(caller).into(),
742			s.parent_bounty_id,
743			Some(s.child_bounty_id),
744		)
745		.is_err());
746
747		Ok(())
748	}
749
750	#[benchmark]
751	fn retry_payment_refund() -> Result<(), BenchmarkError> {
752		let s = create_active_child_bounty::<T, I>()?;
753		let caller = s.curator.clone();
754
755		let new_status = BountyStatus::RefundAttempted {
756			payment_status: PaymentState::Failed,
757			curator: Some(s.child_curator),
758		};
759		let _ = pallet_bounties::Pallet::<T, I>::update_bounty_status(
760			s.parent_bounty_id,
761			Some(s.child_bounty_id),
762			new_status,
763		);
764
765		#[block]
766		{
767			assert_ok!(Bounties::<T, I>::retry_payment(
768				RawOrigin::Signed(caller.clone()).into(),
769				s.parent_bounty_id,
770				Some(s.child_bounty_id),
771			));
772		}
773
774		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
775			.expect("no payment attempt");
776		assert_last_event::<T, I>(
777			Event::Paid {
778				index: s.parent_bounty_id,
779				child_index: Some(s.child_bounty_id),
780				payment_id,
781			}
782			.into(),
783		);
784		assert_ne!(
785			<T as pallet::Config<I>>::Paymaster::check_payment(payment_id),
786			PaymentStatus::Failure
787		);
788		assert!(Bounties::<T, I>::retry_payment(
789			RawOrigin::Signed(caller).into(),
790			s.parent_bounty_id,
791			Some(s.child_bounty_id),
792		)
793		.is_err());
794
795		Ok(())
796	}
797
798	#[benchmark]
799	fn retry_payment_payout() -> Result<(), BenchmarkError> {
800		let s = create_active_child_bounty::<T, I>()?;
801		let caller = s.curator.clone();
802
803		let new_status = BountyStatus::PayoutAttempted {
804			payment_status: PaymentState::Failed,
805			curator: s.child_curator.clone(),
806			beneficiary: s.beneficiary.clone(),
807		};
808		let _ = pallet_bounties::Pallet::<T, I>::update_bounty_status(
809			s.parent_bounty_id,
810			Some(s.child_bounty_id),
811			new_status,
812		);
813
814		#[block]
815		{
816			assert_ok!(Bounties::<T, I>::retry_payment(
817				RawOrigin::Signed(caller.clone()).into(),
818				s.parent_bounty_id,
819				Some(s.child_bounty_id),
820			));
821		}
822
823		let payment_id = get_payment_id::<T, I>(s.parent_bounty_id, Some(s.child_bounty_id))
824			.expect("no payment attempt");
825		assert_last_event::<T, I>(
826			Event::Paid {
827				index: s.parent_bounty_id,
828				child_index: Some(s.child_bounty_id),
829				payment_id,
830			}
831			.into(),
832		);
833		assert_ne!(
834			<T as pallet::Config<I>>::Paymaster::check_payment(payment_id),
835			PaymentStatus::Failure
836		);
837		assert!(Bounties::<T, I>::retry_payment(
838			RawOrigin::Signed(caller).into(),
839			s.parent_bounty_id,
840			Some(s.child_bounty_id),
841		)
842		.is_err());
843
844		Ok(())
845	}
846
847	impl_benchmark_test_suite! {
848		Pallet,
849		crate::mock::ExtBuilder::default().build(),
850		crate::mock::Test
851	}
852}