1#![doc = docify::embed!("src/tests.rs", fund_bounty_works)]
61#![doc = docify::embed!("src/tests.rs", award_bounty_works)]
63#![cfg_attr(not(feature = "std"), no_std)]
69
70mod benchmarking;
71mod mock;
72mod tests;
73pub mod weights;
74#[cfg(feature = "runtime-benchmarks")]
75pub use benchmarking::ArgumentsFactory;
76pub use pallet::*;
77pub use weights::WeightInfo;
78
79extern crate alloc;
80use alloc::{boxed::Box, collections::btree_map::BTreeMap};
81use frame_support::{
82 dispatch::{DispatchResult, DispatchResultWithPostInfo},
83 dispatch_context::with_context,
84 pallet_prelude::*,
85 traits::{
86 tokens::{
87 Balance, ConversionFromAssetBalance, ConversionToAssetBalance, PayWithSource,
88 PaymentStatus,
89 },
90 Consideration, EnsureOrigin, Get, QueryPreimage, StorePreimage,
91 },
92 PalletId,
93};
94use frame_system::pallet_prelude::{
95 ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor,
96};
97use scale_info::TypeInfo;
98use sp_runtime::{
99 traits::{AccountIdConversion, BadOrigin, Convert, Saturating, StaticLookup, TryConvert, Zero},
100 Debug, Permill,
101};
102
103pub type BeneficiaryLookupOf<T, I> = <<T as Config<I>>::BeneficiaryLookup as StaticLookup>::Source;
105pub type BountyIndex = u32;
107pub type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
109pub type PaymentIdOf<T, I = ()> = <<T as crate::Config<I>>::Paymaster as PayWithSource>::Id;
111pub type BountyOf<T, I> = Bounty<
113 <T as frame_system::Config>::AccountId,
114 <T as Config<I>>::Balance,
115 <T as Config<I>>::AssetKind,
116 <T as frame_system::Config>::Hash,
117 PaymentIdOf<T, I>,
118 <T as Config<I>>::Beneficiary,
119>;
120pub type ChildBountyOf<T, I> = ChildBounty<
122 <T as frame_system::Config>::AccountId,
123 <T as Config<I>>::Balance,
124 <T as frame_system::Config>::Hash,
125 PaymentIdOf<T, I>,
126 <T as Config<I>>::Beneficiary,
127>;
128
129#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
131pub struct Bounty<AccountId, Balance, AssetKind, Hash, PaymentId, Beneficiary> {
132 pub asset_kind: AssetKind,
134 pub value: Balance,
139 pub metadata: Hash,
144 pub status: BountyStatus<AccountId, PaymentId, Beneficiary>,
146}
147
148#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
150pub struct ChildBounty<AccountId, Balance, Hash, PaymentId, Beneficiary> {
151 pub parent_bounty: BountyIndex,
153 pub value: Balance,
157 pub metadata: Hash,
162 pub status: BountyStatus<AccountId, PaymentId, Beneficiary>,
164}
165
166#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
168pub enum BountyStatus<AccountId, PaymentId, Beneficiary> {
169 FundingAttempted {
176 curator: AccountId,
178 payment_status: PaymentState<PaymentId>,
181 },
182 Funded {
184 curator: AccountId,
186 },
187 CuratorUnassigned,
191 Active {
196 curator: AccountId,
198 },
199 RefundAttempted {
204 curator: Option<AccountId>,
208 payment_status: PaymentState<PaymentId>,
211 },
212 PayoutAttempted {
218 curator: AccountId,
220 beneficiary: Beneficiary,
222 payment_status: PaymentState<PaymentId>,
224 },
225}
226
227#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, Debug, TypeInfo)]
233pub enum PaymentState<Id> {
234 Pending,
236 Attempted { id: Id },
238 Failed,
240 Succeeded,
242}
243impl<Id: Clone> PaymentState<Id> {
244 pub fn is_pending_or_failed(&self) -> bool {
246 matches!(self, PaymentState::Pending | PaymentState::Failed)
247 }
248
249 pub fn get_attempt_id(&self) -> Option<Id> {
252 match self {
253 PaymentState::Attempted { id } => Some(id.clone()),
254 _ => None,
255 }
256 }
257}
258
259#[frame_support::pallet]
260pub mod pallet {
261 use super::*;
262
263 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
264
265 #[pallet::pallet]
266 #[pallet::storage_version(STORAGE_VERSION)]
267 pub struct Pallet<T, I = ()>(_);
268
269 #[pallet::config]
270 pub trait Config<I: 'static = ()>: frame_system::Config {
271 type Balance: Balance;
273
274 type RejectOrigin: EnsureOrigin<Self::RuntimeOrigin>;
276
277 type SpendOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::Balance>;
280
281 type AssetKind: Parameter + MaxEncodedLen;
284
285 type Beneficiary: Parameter + MaxEncodedLen;
287
288 type BeneficiaryLookup: StaticLookup<Target = Self::Beneficiary>;
290
291 #[pallet::constant]
293 type BountyValueMinimum: Get<Self::Balance>;
294
295 #[pallet::constant]
297 type ChildBountyValueMinimum: Get<Self::Balance>;
298
299 #[pallet::constant]
301 type MaxActiveChildBountyCount: Get<u32>;
302
303 type WeightInfo: WeightInfo;
305
306 type FundingSource: TryConvert<
310 Self::AssetKind,
311 <<Self as pallet::Config<I>>::Paymaster as PayWithSource>::Source,
312 >;
313
314 type BountySource: TryConvert<
318 (BountyIndex, Self::AssetKind),
319 <<Self as pallet::Config<I>>::Paymaster as PayWithSource>::Source,
320 >;
321
322 type ChildBountySource: TryConvert<
328 (BountyIndex, BountyIndex, Self::AssetKind),
329 <<Self as pallet::Config<I>>::Paymaster as PayWithSource>::Source,
330 >;
331
332 type Paymaster: PayWithSource<
335 Balance = Self::Balance,
336 Source = Self::Beneficiary,
337 Beneficiary = Self::Beneficiary,
338 AssetKind = Self::AssetKind,
339 >;
340
341 type BalanceConverter: ConversionFromAssetBalance<Self::Balance, Self::AssetKind, Self::Balance>
348 + ConversionToAssetBalance<Self::Balance, Self::AssetKind, Self::Balance>;
349
350 type Preimages: QueryPreimage<H = Self::Hashing> + StorePreimage;
352
353 type Consideration: Consideration<Self::AccountId, Self::Balance>;
363
364 #[cfg(feature = "runtime-benchmarks")]
366 type BenchmarkHelper: benchmarking::ArgumentsFactory<
367 Self::AssetKind,
368 Self::Beneficiary,
369 Self::Balance,
370 >;
371 }
372
373 #[pallet::error]
374 pub enum Error<T, I = ()> {
375 InvalidIndex,
377 ReasonTooBig,
379 InvalidValue,
381 FailedToConvertBalance,
384 UnexpectedStatus,
386 RequireCurator,
388 InsufficientPermission,
391 FundingError,
393 RefundError,
395 PayoutError,
397 FundingInconclusive,
399 RefundInconclusive,
401 PayoutInconclusive,
403 FailedToConvertSource,
406 HasActiveChildBounty,
408 TooManyChildBounties,
410 InsufficientBountyValue,
412 PreimageNotExist,
414 }
415
416 #[pallet::event]
417 #[pallet::generate_deposit(pub(super) fn deposit_event)]
418 pub enum Event<T: Config<I>, I: 'static = ()> {
419 BountyCreated { index: BountyIndex },
421 ChildBountyCreated { index: BountyIndex, child_index: BountyIndex },
423 BountyBecameActive {
425 index: BountyIndex,
426 child_index: Option<BountyIndex>,
427 curator: T::AccountId,
428 },
429 BountyAwarded {
431 index: BountyIndex,
432 child_index: Option<BountyIndex>,
433 beneficiary: T::Beneficiary,
434 },
435 BountyPayoutProcessed {
437 index: BountyIndex,
438 child_index: Option<BountyIndex>,
439 asset_kind: T::AssetKind,
440 value: T::Balance,
441 beneficiary: T::Beneficiary,
442 },
443 BountyFundingProcessed { index: BountyIndex, child_index: Option<BountyIndex> },
445 BountyRefundProcessed { index: BountyIndex, child_index: Option<BountyIndex> },
447 BountyCanceled { index: BountyIndex, child_index: Option<BountyIndex> },
449 CuratorUnassigned { index: BountyIndex, child_index: Option<BountyIndex> },
451 CuratorProposed {
453 index: BountyIndex,
454 child_index: Option<BountyIndex>,
455 curator: T::AccountId,
456 },
457 PaymentFailed {
459 index: BountyIndex,
460 child_index: Option<BountyIndex>,
461 payment_id: PaymentIdOf<T, I>,
462 },
463 Paid { index: BountyIndex, child_index: Option<BountyIndex>, payment_id: PaymentIdOf<T, I> },
465 }
466
467 #[pallet::composite_enum]
469 pub enum HoldReason<I: 'static = ()> {
470 #[codec(index = 0)]
472 CuratorDeposit,
473 }
474
475 #[pallet::storage]
477 pub type BountyCount<T: Config<I>, I: 'static = ()> = StorageValue<_, u32, ValueQuery>;
478
479 #[pallet::storage]
481 pub type Bounties<T: Config<I>, I: 'static = ()> =
482 StorageMap<_, Twox64Concat, BountyIndex, BountyOf<T, I>>;
483
484 #[pallet::storage]
488 pub type ChildBounties<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
489 _,
490 Twox64Concat,
491 BountyIndex,
492 Twox64Concat,
493 BountyIndex,
494 ChildBountyOf<T, I>,
495 >;
496
497 #[pallet::storage]
501 pub type ChildBountiesPerParent<T: Config<I>, I: 'static = ()> =
502 StorageMap<_, Twox64Concat, BountyIndex, u32, ValueQuery>;
503
504 #[pallet::storage]
508 pub type TotalChildBountiesPerParent<T: Config<I>, I: 'static = ()> =
509 StorageMap<_, Twox64Concat, BountyIndex, u32, ValueQuery>;
510
511 #[pallet::storage]
516 pub type ChildBountiesValuePerParent<T: Config<I>, I: 'static = ()> =
517 StorageMap<_, Twox64Concat, BountyIndex, T::Balance, ValueQuery>;
518
519 #[pallet::storage]
531 pub type CuratorDeposit<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
532 _,
533 Twox64Concat,
534 BountyIndex,
535 Twox64Concat,
536 Option<BountyIndex>,
537 T::Consideration,
538 >;
539
540 #[derive(Default)]
542 pub struct SpendContext<Balance> {
543 pub spend_in_context: BTreeMap<Balance, Balance>,
544 }
545
546 #[pallet::call]
547 impl<T: Config<I>, I: 'static> Pallet<T, I> {
548 #[pallet::call_index(0)]
575 #[pallet::weight(<T as Config<I>>::WeightInfo::fund_bounty())]
576 pub fn fund_bounty(
577 origin: OriginFor<T>,
578 asset_kind: Box<T::AssetKind>,
579 #[pallet::compact] value: T::Balance,
580 curator: AccountIdLookupOf<T>,
581 metadata: T::Hash,
582 ) -> DispatchResult {
583 let max_amount = T::SpendOrigin::ensure_origin(origin)?;
584 let curator = T::Lookup::lookup(curator)?;
585 ensure!(T::Preimages::len(&metadata).is_some(), Error::<T, I>::PreimageNotExist);
586
587 let native_amount = T::BalanceConverter::from_asset_balance(value, *asset_kind.clone())
588 .map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
589 ensure!(native_amount >= T::BountyValueMinimum::get(), Error::<T, I>::InvalidValue);
590 ensure!(native_amount <= max_amount, Error::<T, I>::InsufficientPermission);
591
592 with_context::<SpendContext<T::Balance>, _>(|v| {
593 let context = v.or_default();
594 let funding = context.spend_in_context.entry(max_amount).or_default();
595
596 if funding.checked_add(&native_amount).map(|s| s > max_amount).unwrap_or(true) {
597 Err(Error::<T, I>::InsufficientPermission)
598 } else {
599 *funding = funding.saturating_add(native_amount);
600 Ok(())
601 }
602 })
603 .unwrap_or(Ok(()))?;
604
605 let index = BountyCount::<T, I>::get();
606 let payment_status =
607 Self::do_process_funding_payment(index, None, *asset_kind.clone(), value, None)?;
608
609 let bounty = BountyOf::<T, I> {
610 asset_kind: *asset_kind,
611 value,
612 metadata,
613 status: BountyStatus::FundingAttempted { curator, payment_status },
614 };
615 Bounties::<T, I>::insert(index, &bounty);
616 T::Preimages::request(&metadata);
617 BountyCount::<T, I>::put(index + 1);
618
619 Self::deposit_event(Event::<T, I>::BountyCreated { index });
620
621 Ok(())
622 }
623
624 #[pallet::call_index(1)]
649 #[pallet::weight(<T as Config<I>>::WeightInfo::fund_child_bounty())]
650 pub fn fund_child_bounty(
651 origin: OriginFor<T>,
652 #[pallet::compact] parent_bounty_id: BountyIndex,
653 #[pallet::compact] value: T::Balance,
654 metadata: T::Hash,
655 curator: Option<AccountIdLookupOf<T>>,
656 ) -> DispatchResult {
657 let signer = ensure_signed(origin)?;
658 ensure!(T::Preimages::len(&metadata).is_some(), Error::<T, I>::PreimageNotExist);
659
660 let (asset_kind, parent_value, _, _, parent_curator) =
661 Self::get_bounty_details(parent_bounty_id, None)
662 .map_err(|_| Error::<T, I>::InvalidIndex)?;
663 let native_amount = T::BalanceConverter::from_asset_balance(value, asset_kind.clone())
664 .map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
665
666 ensure!(
667 native_amount >= T::ChildBountyValueMinimum::get(),
668 Error::<T, I>::InvalidValue
669 );
670 ensure!(
671 ChildBountiesPerParent::<T, I>::get(parent_bounty_id) <
672 T::MaxActiveChildBountyCount::get(),
673 Error::<T, I>::TooManyChildBounties,
674 );
675
676 let parent_curator = parent_curator.ok_or(Error::<T, I>::UnexpectedStatus)?;
678 let final_curator = match curator {
679 Some(curator) => T::Lookup::lookup(curator)?,
680 None => parent_curator.clone(),
681 };
682 ensure!(signer == parent_curator, Error::<T, I>::RequireCurator);
683
684 let child_bounties_value = ChildBountiesValuePerParent::<T, I>::get(parent_bounty_id);
686 let remaining_parent_value = parent_value.saturating_sub(child_bounties_value);
687 ensure!(remaining_parent_value >= value, Error::<T, I>::InsufficientBountyValue);
688
689 let child_bounty_id = TotalChildBountiesPerParent::<T, I>::get(parent_bounty_id);
691
692 let payment_status = Self::do_process_funding_payment(
694 parent_bounty_id,
695 Some(child_bounty_id),
696 asset_kind,
697 value,
698 None,
699 )?;
700
701 let child_bounty = ChildBounty {
702 parent_bounty: parent_bounty_id,
703 value,
704 metadata,
705 status: BountyStatus::FundingAttempted {
706 curator: final_curator,
707 payment_status: payment_status.clone(),
708 },
709 };
710 ChildBounties::<T, I>::insert(parent_bounty_id, child_bounty_id, child_bounty);
711 T::Preimages::request(&metadata);
712
713 ChildBountiesValuePerParent::<T, I>::mutate(parent_bounty_id, |children_value| {
717 *children_value = children_value.saturating_add(value)
718 });
719
720 ChildBountiesPerParent::<T, I>::mutate(parent_bounty_id, |count| {
722 count.saturating_inc()
723 });
724 TotalChildBountiesPerParent::<T, I>::insert(
725 parent_bounty_id,
726 child_bounty_id.saturating_add(1),
727 );
728
729 Self::deposit_event(Event::<T, I>::ChildBountyCreated {
730 index: parent_bounty_id,
731 child_index: child_bounty_id,
732 });
733
734 Ok(())
735 }
736
737 #[pallet::call_index(2)]
759 #[pallet::weight(match child_bounty_id {
760 None => <T as Config<I>>::WeightInfo::propose_curator_parent_bounty(),
761 Some(_) => <T as Config<I>>::WeightInfo::propose_curator_child_bounty(),
762 })]
763 pub fn propose_curator(
764 origin: OriginFor<T>,
765 #[pallet::compact] parent_bounty_id: BountyIndex,
766 child_bounty_id: Option<BountyIndex>,
767 curator: AccountIdLookupOf<T>,
768 ) -> DispatchResult {
769 let maybe_sender = ensure_signed(origin.clone())
770 .map(Some)
771 .or_else(|_| T::SpendOrigin::ensure_origin(origin.clone()).map(|_| None))?;
772 let curator = T::Lookup::lookup(curator)?;
773
774 let (asset_kind, value, _, status, parent_curator) =
775 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
776 ensure!(status == BountyStatus::CuratorUnassigned, Error::<T, I>::UnexpectedStatus);
777
778 match child_bounty_id {
779 None => {
781 ensure!(maybe_sender.is_none(), BadOrigin);
782 let max_amount = T::SpendOrigin::ensure_origin(origin)?;
783 let native_amount = T::BalanceConverter::from_asset_balance(value, asset_kind)
784 .map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
785 ensure!(native_amount <= max_amount, Error::<T, I>::InsufficientPermission);
786 },
787 Some(_) => {
789 let parent_curator = parent_curator.ok_or(Error::<T, I>::UnexpectedStatus)?;
790 let sender = maybe_sender.ok_or(BadOrigin)?;
791 ensure!(sender == parent_curator, BadOrigin);
792 },
793 };
794
795 let new_status = BountyStatus::Funded { curator: curator.clone() };
796 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
797
798 Self::deposit_event(Event::<T, I>::CuratorProposed {
799 index: parent_bounty_id,
800 child_index: child_bounty_id,
801 curator,
802 });
803
804 Ok(())
805 }
806
807 #[pallet::call_index(3)]
827 #[pallet::weight(<T as Config<I>>::WeightInfo::accept_curator())]
828 pub fn accept_curator(
829 origin: OriginFor<T>,
830 #[pallet::compact] parent_bounty_id: BountyIndex,
831 child_bounty_id: Option<BountyIndex>,
832 ) -> DispatchResult {
833 let signer = ensure_signed(origin)?;
834
835 let (asset_kind, value, _, status, _) =
836 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
837
838 let BountyStatus::Funded { ref curator } = status else {
839 return Err(Error::<T, I>::UnexpectedStatus.into());
840 };
841 ensure!(signer == *curator, Error::<T, I>::RequireCurator);
842
843 let native_amount = T::BalanceConverter::from_asset_balance(value, asset_kind)
844 .map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
845 let curator_deposit = T::Consideration::new(&curator, native_amount)?;
846 CuratorDeposit::<T, I>::insert(parent_bounty_id, child_bounty_id, curator_deposit);
847
848 let new_status = BountyStatus::Active { curator: curator.clone() };
849 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
850
851 Self::deposit_event(Event::<T, I>::BountyBecameActive {
852 index: parent_bounty_id,
853 child_index: child_bounty_id,
854 curator: signer,
855 });
856
857 Ok(())
858 }
859
860 #[pallet::call_index(4)]
886 #[pallet::weight(<T as Config<I>>::WeightInfo::unassign_curator())]
887 pub fn unassign_curator(
888 origin: OriginFor<T>,
889 #[pallet::compact] parent_bounty_id: BountyIndex,
890 child_bounty_id: Option<BountyIndex>,
891 ) -> DispatchResult {
892 let maybe_sender = ensure_signed(origin.clone())
893 .map(Some)
894 .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?;
895
896 let (_, _, _, status, parent_curator) =
897 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
898
899 match status {
900 BountyStatus::Funded { ref curator } => {
901 ensure!(
905 maybe_sender.map_or(true, |sender| {
906 sender == *curator ||
907 parent_curator
908 .map_or(false, |parent_curator| sender == parent_curator)
909 }),
910 BadOrigin
911 );
912 },
913 BountyStatus::Active { ref curator, .. } => {
914 let maybe_curator_deposit =
915 CuratorDeposit::<T, I>::take(parent_bounty_id, child_bounty_id);
916 match maybe_sender {
918 None => {
920 if let Some(curator_deposit) = maybe_curator_deposit {
921 T::Consideration::burn(curator_deposit, curator);
922 }
923 },
925 Some(sender) if sender == *curator => {
926 if let Some(curator_deposit) = maybe_curator_deposit {
927 T::Consideration::drop(curator_deposit, curator)?;
930 }
931 },
933 Some(sender) => {
934 if let Some(parent_curator) = parent_curator {
935 if sender == parent_curator && *curator != parent_curator {
938 if let Some(curator_deposit) = maybe_curator_deposit {
939 T::Consideration::burn(curator_deposit, curator);
940 }
941 } else {
942 return Err(BadOrigin.into());
943 }
944 }
945 },
946 }
947 },
948 _ => return Err(Error::<T, I>::UnexpectedStatus.into()),
949 };
950
951 let new_status = BountyStatus::CuratorUnassigned;
952 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
953
954 Self::deposit_event(Event::<T, I>::CuratorUnassigned {
955 index: parent_bounty_id,
956 child_index: child_bounty_id,
957 });
958
959 Ok(())
960 }
961
962 #[pallet::call_index(5)]
987 #[pallet::weight(<T as Config<I>>::WeightInfo::award_bounty())]
988 pub fn award_bounty(
989 origin: OriginFor<T>,
990 #[pallet::compact] parent_bounty_id: BountyIndex,
991 child_bounty_id: Option<BountyIndex>,
992 beneficiary: BeneficiaryLookupOf<T, I>,
993 ) -> DispatchResult {
994 let signer = ensure_signed(origin)?;
995 let beneficiary = T::BeneficiaryLookup::lookup(beneficiary)?;
996
997 let (asset_kind, value, _, status, _) =
998 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
999
1000 if child_bounty_id.is_none() {
1001 ensure!(
1002 ChildBountiesPerParent::<T, I>::get(parent_bounty_id) == 0,
1003 Error::<T, I>::HasActiveChildBounty
1004 );
1005 }
1006
1007 let BountyStatus::Active { ref curator } = status else {
1008 return Err(Error::<T, I>::UnexpectedStatus.into());
1009 };
1010 ensure!(signer == *curator, Error::<T, I>::RequireCurator);
1011
1012 let beneficiary_payment_status = Self::do_process_payout_payment(
1013 parent_bounty_id,
1014 child_bounty_id,
1015 asset_kind,
1016 value,
1017 beneficiary.clone(),
1018 None,
1019 )?;
1020
1021 let new_status = BountyStatus::PayoutAttempted {
1022 curator: curator.clone(),
1023 beneficiary: beneficiary.clone(),
1024 payment_status: beneficiary_payment_status.clone(),
1025 };
1026 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
1027
1028 Self::deposit_event(Event::<T, I>::BountyAwarded {
1029 index: parent_bounty_id,
1030 child_index: child_bounty_id,
1031 beneficiary,
1032 });
1033
1034 Ok(())
1035 }
1036
1037 #[pallet::call_index(6)]
1061 #[pallet::weight(match child_bounty_id {
1062 None => <T as Config<I>>::WeightInfo::close_parent_bounty(),
1063 Some(_) => <T as Config<I>>::WeightInfo::close_child_bounty(),
1064 })]
1065 pub fn close_bounty(
1066 origin: OriginFor<T>,
1067 #[pallet::compact] parent_bounty_id: BountyIndex,
1068 child_bounty_id: Option<BountyIndex>,
1069 ) -> DispatchResult {
1070 let maybe_sender = ensure_signed(origin.clone())
1071 .map(Some)
1072 .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?;
1073
1074 let (asset_kind, value, _, status, parent_curator) =
1075 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
1076
1077 let maybe_curator = match status {
1078 BountyStatus::Funded { curator } | BountyStatus::Active { curator, .. } => {
1079 Some(curator)
1080 },
1081 BountyStatus::CuratorUnassigned => None,
1082 _ => return Err(Error::<T, I>::UnexpectedStatus.into()),
1083 };
1084
1085 match child_bounty_id {
1086 None => {
1087 ensure!(
1089 ChildBountiesPerParent::<T, I>::get(parent_bounty_id) == 0,
1090 Error::<T, I>::HasActiveChildBounty
1091 );
1092 if let Some(sender) = maybe_sender.as_ref() {
1094 let is_curator =
1095 maybe_curator.as_ref().map_or(false, |curator| curator == sender);
1096 ensure!(is_curator, BadOrigin);
1097 }
1098 },
1099 Some(_) => {
1100 if let Some(sender) = maybe_sender.as_ref() {
1102 let is_curator =
1103 maybe_curator.as_ref().map_or(false, |curator| curator == sender);
1104 let is_parent_curator = parent_curator
1105 .as_ref()
1106 .map_or(false, |parent_curator| parent_curator == sender);
1107 ensure!(is_curator || is_parent_curator, BadOrigin);
1108 }
1109 },
1110 };
1111
1112 let payment_status = Self::do_process_refund_payment(
1113 parent_bounty_id,
1114 child_bounty_id,
1115 asset_kind,
1116 value,
1117 None,
1118 )?;
1119 let new_status = BountyStatus::RefundAttempted {
1120 payment_status: payment_status.clone(),
1121 curator: maybe_curator.clone(),
1122 };
1123 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
1124
1125 Self::deposit_event(Event::<T, I>::BountyCanceled {
1126 index: parent_bounty_id,
1127 child_index: child_bounty_id,
1128 });
1129
1130 Ok(())
1131 }
1132
1133 #[pallet::call_index(7)]
1159 #[pallet::weight(<T as Config<I>>::WeightInfo::check_status_funding().max(
1160 <T as Config<I>>::WeightInfo::check_status_refund(),
1161 ).max(<T as Config<I>>::WeightInfo::check_status_payout()))]
1162 pub fn check_status(
1163 origin: OriginFor<T>,
1164 #[pallet::compact] parent_bounty_id: BountyIndex,
1165 child_bounty_id: Option<BountyIndex>,
1166 ) -> DispatchResultWithPostInfo {
1167 use BountyStatus::*;
1168
1169 ensure_signed(origin)?;
1170 let (asset_kind, value, metadata, status, parent_curator) =
1171 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
1172
1173 let (new_status, weight) = match status {
1174 FundingAttempted { ref payment_status, curator } => {
1175 let new_payment_status = Self::do_check_funding_payment_status(
1176 parent_bounty_id,
1177 child_bounty_id,
1178 payment_status.clone(),
1179 )?;
1180
1181 let new_status = match new_payment_status {
1182 PaymentState::Succeeded => match (child_bounty_id, parent_curator) {
1183 (Some(_), Some(parent_curator)) if curator == parent_curator => {
1184 BountyStatus::Active { curator }
1185 },
1186 _ => BountyStatus::Funded { curator },
1187 },
1188 PaymentState::Pending |
1189 PaymentState::Failed |
1190 PaymentState::Attempted { .. } => BountyStatus::FundingAttempted {
1191 payment_status: new_payment_status,
1192 curator,
1193 },
1194 };
1195
1196 let weight = <T as Config<I>>::WeightInfo::check_status_funding();
1197
1198 (new_status, weight)
1199 },
1200 RefundAttempted { ref payment_status, ref curator } => {
1201 let new_payment_status = Self::do_check_refund_payment_status(
1202 parent_bounty_id,
1203 child_bounty_id,
1204 payment_status.clone(),
1205 )?;
1206
1207 let new_status = match new_payment_status {
1208 PaymentState::Succeeded => {
1209 if let Some(curator) = curator {
1210 if let Some(curator_deposit) =
1214 CuratorDeposit::<T, I>::take(parent_bounty_id, child_bounty_id)
1215 {
1216 T::Consideration::drop(curator_deposit, curator)?;
1217 }
1218 }
1219 if let Some(_) = child_bounty_id {
1220 ChildBountiesValuePerParent::<T, I>::mutate(
1222 parent_bounty_id,
1223 |total_value| *total_value = total_value.saturating_sub(value),
1224 );
1225 }
1226 Self::remove_bounty(parent_bounty_id, child_bounty_id, metadata);
1228 return Ok(Pays::No.into());
1229 },
1230 PaymentState::Pending |
1231 PaymentState::Failed |
1232 PaymentState::Attempted { .. } => BountyStatus::RefundAttempted {
1233 payment_status: new_payment_status,
1234 curator: curator.clone(),
1235 },
1236 };
1237
1238 let weight = <T as Config<I>>::WeightInfo::check_status_refund();
1239
1240 (new_status, weight)
1241 },
1242 PayoutAttempted { ref curator, ref beneficiary, ref payment_status } => {
1243 let new_payment_status = Self::do_check_payout_payment_status(
1244 parent_bounty_id,
1245 child_bounty_id,
1246 asset_kind,
1247 value,
1248 beneficiary.clone(),
1249 payment_status.clone(),
1250 )?;
1251
1252 let new_status = match new_payment_status {
1253 PaymentState::Succeeded => {
1254 if let Some(curator_deposit) =
1255 CuratorDeposit::<T, I>::take(parent_bounty_id, child_bounty_id)
1256 {
1257 T::Consideration::drop(curator_deposit, curator)?;
1261 }
1262 Self::remove_bounty(parent_bounty_id, child_bounty_id, metadata);
1264 return Ok(Pays::No.into());
1265 },
1266 PaymentState::Pending |
1267 PaymentState::Failed |
1268 PaymentState::Attempted { .. } => BountyStatus::PayoutAttempted {
1269 curator: curator.clone(),
1270 beneficiary: beneficiary.clone(),
1271 payment_status: new_payment_status.clone(),
1272 },
1273 };
1274
1275 let weight = <T as Config<I>>::WeightInfo::check_status_payout();
1276
1277 (new_status, weight)
1278 },
1279 _ => return Err(Error::<T, I>::UnexpectedStatus.into()),
1280 };
1281
1282 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
1283
1284 Ok(Some(weight).into())
1285 }
1286
1287 #[pallet::call_index(8)]
1310 #[pallet::weight(<T as Config<I>>::WeightInfo::retry_payment_funding().max(
1311 <T as Config<I>>::WeightInfo::retry_payment_refund(),
1312 ).max(<T as Config<I>>::WeightInfo::retry_payment_payout()))]
1313 pub fn retry_payment(
1314 origin: OriginFor<T>,
1315 #[pallet::compact] parent_bounty_id: BountyIndex,
1316 child_bounty_id: Option<BountyIndex>,
1317 ) -> DispatchResultWithPostInfo {
1318 use BountyStatus::*;
1319
1320 ensure_signed(origin)?;
1321 let (asset_kind, value, _, status, _) =
1322 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
1323
1324 let (new_status, weight) = match status {
1325 FundingAttempted { ref payment_status, ref curator } => {
1326 let new_payment_status = Self::do_process_funding_payment(
1327 parent_bounty_id,
1328 child_bounty_id,
1329 asset_kind,
1330 value,
1331 Some(payment_status.clone()),
1332 )?;
1333
1334 (
1335 FundingAttempted {
1336 payment_status: new_payment_status,
1337 curator: curator.clone(),
1338 },
1339 <T as Config<I>>::WeightInfo::retry_payment_funding(),
1340 )
1341 },
1342 RefundAttempted { ref curator, ref payment_status } => {
1343 let new_payment_status = Self::do_process_refund_payment(
1344 parent_bounty_id,
1345 child_bounty_id,
1346 asset_kind,
1347 value,
1348 Some(payment_status.clone()),
1349 )?;
1350 (
1351 RefundAttempted {
1352 curator: curator.clone(),
1353 payment_status: new_payment_status,
1354 },
1355 <T as Config<I>>::WeightInfo::retry_payment_refund(),
1356 )
1357 },
1358 PayoutAttempted { ref curator, ref beneficiary, ref payment_status } => {
1359 let new_payment_status = Self::do_process_payout_payment(
1360 parent_bounty_id,
1361 child_bounty_id,
1362 asset_kind,
1363 value,
1364 beneficiary.clone(),
1365 Some(payment_status.clone()),
1366 )?;
1367 (
1368 PayoutAttempted {
1369 curator: curator.clone(),
1370 beneficiary: beneficiary.clone(),
1371 payment_status: new_payment_status,
1372 },
1373 <T as Config<I>>::WeightInfo::retry_payment_payout(),
1374 )
1375 },
1376 _ => return Err(Error::<T, I>::UnexpectedStatus.into()),
1377 };
1378
1379 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
1380
1381 Ok(Some(weight).into())
1382 }
1383 }
1384
1385 #[pallet::hooks]
1386 impl<T: Config<I>, I: 'static> Hooks<SystemBlockNumberFor<T>> for Pallet<T, I> {
1387 #[cfg(feature = "try-runtime")]
1388 fn try_state(_n: SystemBlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
1389 Self::do_try_state()
1390 }
1391 }
1392}
1393
1394#[cfg(any(feature = "try-runtime", test))]
1395impl<T: Config<I>, I: 'static> Pallet<T, I> {
1396 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
1400 Self::try_state_bounties_count()?;
1401
1402 for parent_bounty_id in Bounties::<T, I>::iter_keys() {
1403 Self::try_state_child_bounties_count(parent_bounty_id)?;
1404 }
1405
1406 Ok(())
1407 }
1408
1409 fn try_state_bounties_count() -> Result<(), sp_runtime::TryRuntimeError> {
1414 let bounties_length = Bounties::<T, I>::iter().count() as u32;
1415
1416 ensure!(
1417 <BountyCount<T, I>>::get() >= bounties_length,
1418 "`BountyCount` must be grater or equals the number of `Bounties` in storage"
1419 );
1420
1421 Ok(())
1422 }
1423
1424 fn try_state_child_bounties_count(
1429 parent_bounty_id: BountyIndex,
1430 ) -> Result<(), sp_runtime::TryRuntimeError> {
1431 let child_bounties_length =
1432 ChildBounties::<T, I>::iter_prefix(parent_bounty_id).count() as u32;
1433
1434 ensure!(
1435 <ChildBountiesPerParent<T, I>>::get(parent_bounty_id) >= child_bounties_length,
1436 "`ChildBountiesPerParent` must be grater or equals the number of `ChildBounties` in storage"
1437 );
1438
1439 Ok(())
1440 }
1441}
1442
1443impl<T: Config<I>, I: 'static> Pallet<T, I> {
1444 pub fn funding_source_account(
1446 asset_kind: T::AssetKind,
1447 ) -> Result<T::Beneficiary, DispatchError> {
1448 T::FundingSource::try_convert(asset_kind)
1449 .map_err(|_| Error::<T, I>::FailedToConvertSource.into())
1450 }
1451
1452 pub fn bounty_account(
1454 bounty_id: BountyIndex,
1455 asset_kind: T::AssetKind,
1456 ) -> Result<T::Beneficiary, DispatchError> {
1457 T::BountySource::try_convert((bounty_id, asset_kind))
1458 .map_err(|_| Error::<T, I>::FailedToConvertSource.into())
1459 }
1460
1461 pub fn child_bounty_account(
1463 parent_bounty_id: BountyIndex,
1464 child_bounty_id: BountyIndex,
1465 asset_kind: T::AssetKind,
1466 ) -> Result<T::Beneficiary, DispatchError> {
1467 T::ChildBountySource::try_convert((parent_bounty_id, child_bounty_id, asset_kind))
1468 .map_err(|_| Error::<T, I>::FailedToConvertSource.into())
1469 }
1470
1471 pub fn get_bounty_details(
1476 parent_bounty_id: BountyIndex,
1477 child_bounty_id: Option<BountyIndex>,
1478 ) -> Result<
1479 (
1480 T::AssetKind,
1481 T::Balance,
1482 T::Hash,
1483 BountyStatus<T::AccountId, PaymentIdOf<T, I>, T::Beneficiary>,
1484 Option<T::AccountId>,
1485 ),
1486 DispatchError,
1487 > {
1488 let parent_bounty =
1489 Bounties::<T, I>::get(parent_bounty_id).ok_or(Error::<T, I>::InvalidIndex)?;
1490
1491 let parent_curator = if let BountyStatus::Active { curator } = &parent_bounty.status {
1493 Some(curator.clone())
1494 } else {
1495 None
1496 };
1497
1498 match child_bounty_id {
1499 None => Ok((
1500 parent_bounty.asset_kind,
1501 parent_bounty.value,
1502 parent_bounty.metadata,
1503 parent_bounty.status,
1504 parent_curator,
1505 )),
1506 Some(child_bounty_id) => {
1507 let child_bounty = ChildBounties::<T, I>::get(parent_bounty_id, child_bounty_id)
1508 .ok_or(Error::<T, I>::InvalidIndex)?;
1509 Ok((
1510 parent_bounty.asset_kind,
1511 child_bounty.value,
1512 child_bounty.metadata,
1513 child_bounty.status,
1514 parent_curator,
1515 ))
1516 },
1517 }
1518 }
1519
1520 pub fn update_bounty_status(
1522 parent_bounty_id: BountyIndex,
1523 child_bounty_id: Option<BountyIndex>,
1524 new_status: BountyStatus<T::AccountId, PaymentIdOf<T, I>, T::Beneficiary>,
1525 ) -> Result<(), DispatchError> {
1526 match child_bounty_id {
1527 None => {
1528 let mut bounty =
1529 Bounties::<T, I>::get(parent_bounty_id).ok_or(Error::<T, I>::InvalidIndex)?;
1530 bounty.status = new_status;
1531 Bounties::<T, I>::insert(parent_bounty_id, bounty);
1532 },
1533 Some(child_bounty_id) => {
1534 let mut bounty = ChildBounties::<T, I>::get(parent_bounty_id, child_bounty_id)
1535 .ok_or(Error::<T, I>::InvalidIndex)?;
1536 bounty.status = new_status;
1537 ChildBounties::<T, I>::insert(parent_bounty_id, child_bounty_id, bounty);
1538 },
1539 }
1540
1541 Ok(())
1542 }
1543
1544 fn calculate_payout(
1546 parent_bounty_id: BountyIndex,
1547 child_bounty_id: Option<BountyIndex>,
1548 value: T::Balance,
1549 ) -> T::Balance {
1550 match child_bounty_id {
1551 None => {
1552 let children_value = ChildBountiesValuePerParent::<T, I>::take(parent_bounty_id);
1555 debug_assert!(children_value <= value);
1556 let payout = value.saturating_sub(children_value);
1557 payout
1558 },
1559 Some(_) => value,
1560 }
1561 }
1562
1563 fn remove_bounty(
1565 parent_bounty_id: BountyIndex,
1566 child_bounty_id: Option<BountyIndex>,
1567 metadata: T::Hash,
1568 ) {
1569 match child_bounty_id {
1570 None => {
1571 Bounties::<T, I>::remove(parent_bounty_id);
1572 ChildBountiesPerParent::<T, I>::remove(parent_bounty_id);
1573 TotalChildBountiesPerParent::<T, I>::remove(parent_bounty_id);
1574 debug_assert!(ChildBountiesValuePerParent::<T, I>::get(parent_bounty_id).is_zero());
1575 },
1576 Some(child_bounty_id) => {
1577 ChildBounties::<T, I>::remove(parent_bounty_id, child_bounty_id);
1578 ChildBountiesPerParent::<T, I>::mutate(parent_bounty_id, |count| {
1579 count.saturating_dec()
1580 });
1581 },
1582 }
1583
1584 T::Preimages::unrequest(&metadata);
1585 }
1586
1587 fn do_process_funding_payment(
1589 parent_bounty_id: BountyIndex,
1590 child_bounty_id: Option<BountyIndex>,
1591 asset_kind: T::AssetKind,
1592 value: T::Balance,
1593 maybe_payment_status: Option<PaymentState<PaymentIdOf<T, I>>>,
1594 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1595 if let Some(payment_status) = maybe_payment_status {
1596 ensure!(payment_status.is_pending_or_failed(), Error::<T, I>::UnexpectedStatus);
1597 }
1598
1599 let (source, beneficiary) = match child_bounty_id {
1600 None => (
1601 Self::funding_source_account(asset_kind.clone())?,
1602 Self::bounty_account(parent_bounty_id, asset_kind.clone())?,
1603 ),
1604 Some(child_bounty_id) => (
1605 Self::bounty_account(parent_bounty_id, asset_kind.clone())?,
1606 Self::child_bounty_account(parent_bounty_id, child_bounty_id, asset_kind.clone())?,
1607 ),
1608 };
1609
1610 let id = <T as Config<I>>::Paymaster::pay(&source, &beneficiary, asset_kind, value)
1611 .map_err(|_| Error::<T, I>::FundingError)?;
1612
1613 Self::deposit_event(Event::<T, I>::Paid {
1614 index: parent_bounty_id,
1615 child_index: child_bounty_id,
1616 payment_id: id,
1617 });
1618
1619 Ok(PaymentState::Attempted { id })
1620 }
1621
1622 fn do_check_funding_payment_status(
1625 parent_bounty_id: BountyIndex,
1626 child_bounty_id: Option<BountyIndex>,
1627 payment_status: PaymentState<PaymentIdOf<T, I>>,
1628 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1629 let payment_id = payment_status.get_attempt_id().ok_or(Error::<T, I>::UnexpectedStatus)?;
1630
1631 match <T as Config<I>>::Paymaster::check_payment(payment_id) {
1632 PaymentStatus::Success => {
1633 Self::deposit_event(Event::<T, I>::BountyFundingProcessed {
1634 index: parent_bounty_id,
1635 child_index: child_bounty_id,
1636 });
1637 Ok(PaymentState::Succeeded)
1638 },
1639 PaymentStatus::InProgress | PaymentStatus::Unknown => {
1640 return Err(Error::<T, I>::FundingInconclusive.into())
1641 },
1642 PaymentStatus::Failure => {
1643 Self::deposit_event(Event::<T, I>::PaymentFailed {
1644 index: parent_bounty_id,
1645 child_index: child_bounty_id,
1646 payment_id,
1647 });
1648 return Ok(PaymentState::Failed);
1649 },
1650 }
1651 }
1652
1653 fn do_process_refund_payment(
1656 parent_bounty_id: BountyIndex,
1657 child_bounty_id: Option<BountyIndex>,
1658 asset_kind: T::AssetKind,
1659 value: T::Balance,
1660 payment_status: Option<PaymentState<PaymentIdOf<T, I>>>,
1661 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1662 if let Some(payment_status) = payment_status {
1663 ensure!(payment_status.is_pending_or_failed(), Error::<T, I>::UnexpectedStatus);
1664 }
1665
1666 let (source, beneficiary) = match child_bounty_id {
1667 None => (
1668 Self::bounty_account(parent_bounty_id, asset_kind.clone())?,
1669 Self::funding_source_account(asset_kind.clone())?,
1670 ),
1671 Some(child_bounty_id) => (
1672 Self::child_bounty_account(parent_bounty_id, child_bounty_id, asset_kind.clone())?,
1673 Self::bounty_account(parent_bounty_id, asset_kind.clone())?,
1674 ),
1675 };
1676
1677 let id = <T as Config<I>>::Paymaster::pay(&source, &beneficiary, asset_kind, value)
1678 .map_err(|_| Error::<T, I>::RefundError)?;
1679
1680 Self::deposit_event(Event::<T, I>::Paid {
1681 index: parent_bounty_id,
1682 child_index: child_bounty_id,
1683 payment_id: id,
1684 });
1685
1686 Ok(PaymentState::Attempted { id })
1687 }
1688
1689 fn do_check_refund_payment_status(
1692 parent_bounty_id: BountyIndex,
1693 child_bounty_id: Option<BountyIndex>,
1694 payment_status: PaymentState<PaymentIdOf<T, I>>,
1695 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1696 let payment_id = payment_status.get_attempt_id().ok_or(Error::<T, I>::UnexpectedStatus)?;
1697
1698 match <T as pallet::Config<I>>::Paymaster::check_payment(payment_id) {
1699 PaymentStatus::Success => {
1700 Self::deposit_event(Event::<T, I>::BountyRefundProcessed {
1701 index: parent_bounty_id,
1702 child_index: child_bounty_id,
1703 });
1704 Ok(PaymentState::Succeeded)
1705 },
1706 PaymentStatus::InProgress | PaymentStatus::Unknown =>
1707 {
1709 Err(Error::<T, I>::RefundInconclusive.into())
1710 },
1711 PaymentStatus::Failure => {
1712 Self::deposit_event(Event::<T, I>::PaymentFailed {
1714 index: parent_bounty_id,
1715 child_index: child_bounty_id,
1716 payment_id,
1717 });
1718 Ok(PaymentState::Failed)
1719 },
1720 }
1721 }
1722
1723 fn do_process_payout_payment(
1725 parent_bounty_id: BountyIndex,
1726 child_bounty_id: Option<BountyIndex>,
1727 asset_kind: T::AssetKind,
1728 value: T::Balance,
1729 beneficiary: T::Beneficiary,
1730 payment_status: Option<PaymentState<PaymentIdOf<T, I>>>,
1731 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1732 if let Some(payment_status) = payment_status {
1733 ensure!(payment_status.is_pending_or_failed(), Error::<T, I>::UnexpectedStatus);
1734 }
1735
1736 let payout = Self::calculate_payout(parent_bounty_id, child_bounty_id, value);
1737
1738 let source = match child_bounty_id {
1739 None => Self::bounty_account(parent_bounty_id, asset_kind.clone())?,
1740 Some(child_bounty_id) => {
1741 Self::child_bounty_account(parent_bounty_id, child_bounty_id, asset_kind.clone())?
1742 },
1743 };
1744
1745 let id = <T as Config<I>>::Paymaster::pay(&source, &beneficiary, asset_kind, payout)
1746 .map_err(|_| Error::<T, I>::PayoutError)?;
1747
1748 Self::deposit_event(Event::<T, I>::Paid {
1749 index: parent_bounty_id,
1750 child_index: child_bounty_id,
1751 payment_id: id,
1752 });
1753
1754 Ok(PaymentState::Attempted { id })
1755 }
1756
1757 fn do_check_payout_payment_status(
1760 parent_bounty_id: BountyIndex,
1761 child_bounty_id: Option<BountyIndex>,
1762 asset_kind: T::AssetKind,
1763 value: T::Balance,
1764 beneficiary: T::Beneficiary,
1765 payment_status: PaymentState<PaymentIdOf<T, I>>,
1766 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1767 let payment_id = payment_status.get_attempt_id().ok_or(Error::<T, I>::UnexpectedStatus)?;
1768
1769 match <T as pallet::Config<I>>::Paymaster::check_payment(payment_id) {
1770 PaymentStatus::Success => {
1771 let payout = Self::calculate_payout(parent_bounty_id, child_bounty_id, value);
1772
1773 Self::deposit_event(Event::<T, I>::BountyPayoutProcessed {
1774 index: parent_bounty_id,
1775 child_index: child_bounty_id,
1776 asset_kind: asset_kind.clone(),
1777 value: payout,
1778 beneficiary,
1779 });
1780
1781 Ok(PaymentState::Succeeded)
1782 },
1783 PaymentStatus::InProgress | PaymentStatus::Unknown =>
1784 {
1786 Err(Error::<T, I>::PayoutInconclusive.into())
1787 },
1788 PaymentStatus::Failure => {
1789 Self::deposit_event(Event::<T, I>::PaymentFailed {
1791 index: parent_bounty_id,
1792 child_index: child_bounty_id,
1793 payment_id,
1794 });
1795 Ok(PaymentState::Failed)
1796 },
1797 }
1798 }
1799}
1800
1801pub struct CuratorDepositAmount<Mult, Min, Max, Balance>(PhantomData<(Mult, Min, Max, Balance)>);
1806impl<Mult, Min, Max, Balance> Convert<Balance, Balance>
1807 for CuratorDepositAmount<Mult, Min, Max, Balance>
1808where
1809 Balance: frame_support::traits::tokens::Balance,
1810 Min: Get<Option<Balance>>,
1811 Max: Get<Option<Balance>>,
1812 Mult: Get<Permill>,
1813{
1814 fn convert(value: Balance) -> Balance {
1815 let mut deposit = Mult::get().mul_floor(value);
1816
1817 if let Some(min) = Min::get() {
1818 if deposit < min {
1819 deposit = min;
1820 }
1821 }
1822
1823 if let Some(max) = Max::get() {
1824 if deposit > max {
1825 deposit = max;
1826 }
1827 }
1828
1829 deposit
1830 }
1831}
1832
1833pub struct PalletIdAsFundingSource<Id, T, C, I = ()>(PhantomData<(Id, T, C, I)>);
1844impl<Id, T, C, I> TryConvert<T::AssetKind, T::Beneficiary> for PalletIdAsFundingSource<Id, T, C, I>
1845where
1846 Id: Get<PalletId>,
1847 T: crate::Config<I>,
1848 C: Convert<T::AccountId, T::Beneficiary>,
1849{
1850 fn try_convert(_asset_kind: T::AssetKind) -> Result<T::Beneficiary, T::AssetKind> {
1851 let account: T::AccountId = Id::get().into_account_truncating();
1852 Ok(C::convert(account))
1853 }
1854}
1855
1856pub struct BountySourceFromPalletId<Id, T, C, I = ()>(PhantomData<(Id, T, C, I)>);
1867impl<Id, T, C, I> TryConvert<(BountyIndex, T::AssetKind), T::Beneficiary>
1868 for BountySourceFromPalletId<Id, T, C, I>
1869where
1870 Id: Get<PalletId>,
1871 T: crate::Config<I>,
1872 C: Convert<T::AccountId, T::Beneficiary>,
1873{
1874 fn try_convert(
1875 (parent_bounty_id, _asset_kind): (BountyIndex, T::AssetKind),
1876 ) -> Result<T::Beneficiary, (BountyIndex, T::AssetKind)> {
1877 let account: T::AccountId = Id::get().into_sub_account_truncating(("bt", parent_bounty_id));
1878 Ok(C::convert(account))
1879 }
1880}
1881
1882pub struct ChildBountySourceFromPalletId<Id, T, C, I = ()>(PhantomData<(Id, T, C, I)>);
1893impl<Id, T, C, I> TryConvert<(BountyIndex, BountyIndex, T::AssetKind), T::Beneficiary>
1894 for ChildBountySourceFromPalletId<Id, T, C, I>
1895where
1896 Id: Get<PalletId>,
1897 T: crate::Config<I>,
1898 C: Convert<T::AccountId, T::Beneficiary>,
1899{
1900 fn try_convert(
1901 (parent_bounty_id, child_bounty_id, _asset_kind): (BountyIndex, BountyIndex, T::AssetKind),
1902 ) -> Result<T::Beneficiary, (BountyIndex, BountyIndex, T::AssetKind)> {
1903 let account: T::AccountId =
1906 Id::get().into_sub_account_truncating(("cb", parent_bounty_id, child_bounty_id));
1907 Ok(C::convert(account))
1908 }
1909}