1#![doc = docify::embed!("src/tests.rs", fund_bounty_works)]
66#![doc = docify::embed!("src/tests.rs", award_bounty_works)]
68#![cfg_attr(not(feature = "std"), no_std)]
74
75mod benchmarking;
76mod mock;
77mod tests;
78pub mod weights;
79#[cfg(feature = "runtime-benchmarks")]
80pub use benchmarking::ArgumentsFactory;
81pub use pallet::*;
82pub use weights::WeightInfo;
83
84extern crate alloc;
85use alloc::{boxed::Box, collections::btree_map::BTreeMap};
86use frame_support::{
87 dispatch::{DispatchResult, DispatchResultWithPostInfo},
88 dispatch_context::with_context,
89 pallet_prelude::*,
90 traits::{
91 tokens::{
92 Balance, ConversionFromAssetBalance, ConversionToAssetBalance, PayWithSource,
93 PaymentStatus,
94 },
95 Consideration, EnsureOrigin, Get, QueryPreimage, StorePreimage,
96 },
97 PalletId,
98};
99use frame_system::pallet_prelude::{
100 ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor,
101};
102use scale_info::TypeInfo;
103use sp_runtime::{
104 traits::{AccountIdConversion, BadOrigin, Convert, Saturating, StaticLookup, TryConvert},
105 Debug, Permill,
106};
107
108pub type BeneficiaryLookupOf<T, I> = <<T as Config<I>>::BeneficiaryLookup as StaticLookup>::Source;
110pub type BountyIndex = u32;
112pub type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
114pub type PaymentIdOf<T, I = ()> = <<T as crate::Config<I>>::Paymaster as PayWithSource>::Id;
116pub type BountyOf<T, I> = Bounty<
118 <T as frame_system::Config>::AccountId,
119 <T as Config<I>>::Balance,
120 <T as Config<I>>::AssetKind,
121 <T as frame_system::Config>::Hash,
122 PaymentIdOf<T, I>,
123 <T as Config<I>>::Beneficiary,
124>;
125pub type ChildBountyOf<T, I> = ChildBounty<
127 <T as frame_system::Config>::AccountId,
128 <T as Config<I>>::Balance,
129 <T as frame_system::Config>::Hash,
130 PaymentIdOf<T, I>,
131 <T as Config<I>>::Beneficiary,
132>;
133
134#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
136pub struct Bounty<AccountId, Balance, AssetKind, Hash, PaymentId, Beneficiary> {
137 pub asset_kind: AssetKind,
139 pub value: Balance,
144 pub metadata: Hash,
149 pub status: BountyStatus<AccountId, PaymentId, Beneficiary>,
151}
152
153#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
155pub struct ChildBounty<AccountId, Balance, Hash, PaymentId, Beneficiary> {
156 pub parent_bounty: BountyIndex,
158 pub value: Balance,
162 pub metadata: Hash,
167 pub status: BountyStatus<AccountId, PaymentId, Beneficiary>,
169}
170
171#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
173pub enum BountyStatus<AccountId, PaymentId, Beneficiary> {
174 FundingAttempted {
181 curator: AccountId,
183 payment_status: PaymentState<PaymentId>,
186 },
187 Funded {
189 curator: AccountId,
191 },
192 CuratorUnassigned,
196 Active {
201 curator: AccountId,
203 },
204 RefundAttempted {
209 curator: Option<AccountId>,
213 payment_status: PaymentState<PaymentId>,
216 },
217 PayoutAttempted {
223 curator: AccountId,
225 beneficiary: Beneficiary,
227 payment_status: PaymentState<PaymentId>,
229 },
230}
231
232#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, Debug, TypeInfo)]
238pub enum PaymentState<Id> {
239 Pending,
241 Attempted { id: Id },
243 Failed,
245 Succeeded,
247}
248impl<Id: Clone> PaymentState<Id> {
249 pub fn is_pending_or_failed(&self) -> bool {
251 matches!(self, PaymentState::Pending | PaymentState::Failed)
252 }
253
254 pub fn get_attempt_id(&self) -> Option<Id> {
257 match self {
258 PaymentState::Attempted { id } => Some(id.clone()),
259 _ => None,
260 }
261 }
262}
263
264#[frame_support::pallet]
265pub mod pallet {
266 use super::*;
267
268 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
269
270 #[pallet::pallet]
271 #[pallet::storage_version(STORAGE_VERSION)]
272 pub struct Pallet<T, I = ()>(_);
273
274 #[pallet::config]
275 pub trait Config<I: 'static = ()>: frame_system::Config {
276 type Balance: Balance;
278
279 type RejectOrigin: EnsureOrigin<Self::RuntimeOrigin>;
281
282 type SpendOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::Balance>;
285
286 type AssetKind: Parameter + MaxEncodedLen;
289
290 type Beneficiary: Parameter + MaxEncodedLen;
292
293 type BeneficiaryLookup: StaticLookup<Target = Self::Beneficiary>;
295
296 #[pallet::constant]
298 type BountyValueMinimum: Get<Self::Balance>;
299
300 #[pallet::constant]
302 type ChildBountyValueMinimum: Get<Self::Balance>;
303
304 #[pallet::constant]
306 type MaxActiveChildBountyCount: Get<u32>;
307
308 type WeightInfo: WeightInfo;
310
311 type FundingSource: TryConvert<
315 Self::AssetKind,
316 <<Self as pallet::Config<I>>::Paymaster as PayWithSource>::Source,
317 >;
318
319 type BountySource: TryConvert<
323 (BountyIndex, Self::AssetKind),
324 <<Self as pallet::Config<I>>::Paymaster as PayWithSource>::Source,
325 >;
326
327 type ChildBountySource: TryConvert<
333 (BountyIndex, BountyIndex, Self::AssetKind),
334 <<Self as pallet::Config<I>>::Paymaster as PayWithSource>::Source,
335 >;
336
337 type Paymaster: PayWithSource<
340 Balance = Self::Balance,
341 Source = Self::Beneficiary,
342 Beneficiary = Self::Beneficiary,
343 AssetKind = Self::AssetKind,
344 >;
345
346 type BalanceConverter: ConversionFromAssetBalance<Self::Balance, Self::AssetKind, Self::Balance>
353 + ConversionToAssetBalance<Self::Balance, Self::AssetKind, Self::Balance>;
354
355 type Preimages: QueryPreimage<H = Self::Hashing> + StorePreimage;
357
358 type Consideration: Consideration<Self::AccountId, Self::Balance>;
368
369 #[cfg(feature = "runtime-benchmarks")]
371 type BenchmarkHelper: benchmarking::ArgumentsFactory<
372 Self::AssetKind,
373 Self::Beneficiary,
374 Self::Balance,
375 >;
376 }
377
378 #[pallet::error]
379 pub enum Error<T, I = ()> {
380 InvalidIndex,
382 ReasonTooBig,
384 InvalidValue,
386 FailedToConvertBalance,
389 UnexpectedStatus,
391 RequireCurator,
393 InsufficientPermission,
396 FundingError,
398 RefundError,
400 PayoutError,
402 FundingInconclusive,
404 RefundInconclusive,
406 PayoutInconclusive,
408 FailedToConvertSource,
411 HasActiveChildBounty,
413 TooManyChildBounties,
415 InsufficientBountyValue,
417 PreimageNotExist,
419 }
420
421 #[pallet::event]
422 #[pallet::generate_deposit(pub(super) fn deposit_event)]
423 pub enum Event<T: Config<I>, I: 'static = ()> {
424 BountyCreated { index: BountyIndex },
426 ChildBountyCreated { index: BountyIndex, child_index: BountyIndex },
428 BountyBecameActive {
430 index: BountyIndex,
431 child_index: Option<BountyIndex>,
432 curator: T::AccountId,
433 },
434 BountyAwarded {
436 index: BountyIndex,
437 child_index: Option<BountyIndex>,
438 beneficiary: T::Beneficiary,
439 },
440 BountyPayoutProcessed {
442 index: BountyIndex,
443 child_index: Option<BountyIndex>,
444 asset_kind: T::AssetKind,
445 value: T::Balance,
446 beneficiary: T::Beneficiary,
447 },
448 BountyFundingProcessed { index: BountyIndex, child_index: Option<BountyIndex> },
450 BountyRefundProcessed { index: BountyIndex, child_index: Option<BountyIndex> },
452 BountyCanceled { index: BountyIndex, child_index: Option<BountyIndex> },
454 CuratorUnassigned { index: BountyIndex, child_index: Option<BountyIndex> },
456 CuratorProposed {
458 index: BountyIndex,
459 child_index: Option<BountyIndex>,
460 curator: T::AccountId,
461 },
462 PaymentFailed {
464 index: BountyIndex,
465 child_index: Option<BountyIndex>,
466 payment_id: PaymentIdOf<T, I>,
467 },
468 Paid { index: BountyIndex, child_index: Option<BountyIndex>, payment_id: PaymentIdOf<T, I> },
470 }
471
472 #[pallet::composite_enum]
474 pub enum HoldReason<I: 'static = ()> {
475 #[codec(index = 0)]
477 CuratorDeposit,
478 }
479
480 #[pallet::storage]
482 pub type BountyCount<T: Config<I>, I: 'static = ()> = StorageValue<_, u32, ValueQuery>;
483
484 #[pallet::storage]
486 pub type Bounties<T: Config<I>, I: 'static = ()> =
487 StorageMap<_, Twox64Concat, BountyIndex, BountyOf<T, I>>;
488
489 #[pallet::storage]
493 pub type ChildBounties<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
494 _,
495 Twox64Concat,
496 BountyIndex,
497 Twox64Concat,
498 BountyIndex,
499 ChildBountyOf<T, I>,
500 >;
501
502 #[pallet::storage]
506 pub type ChildBountiesPerParent<T: Config<I>, I: 'static = ()> =
507 StorageMap<_, Twox64Concat, BountyIndex, u32, ValueQuery>;
508
509 #[pallet::storage]
513 pub type TotalChildBountiesPerParent<T: Config<I>, I: 'static = ()> =
514 StorageMap<_, Twox64Concat, BountyIndex, u32, ValueQuery>;
515
516 #[pallet::storage]
521 pub type ChildBountiesValuePerParent<T: Config<I>, I: 'static = ()> =
522 StorageMap<_, Twox64Concat, BountyIndex, T::Balance, ValueQuery>;
523
524 #[pallet::storage]
536 pub type CuratorDeposit<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
537 _,
538 Twox64Concat,
539 BountyIndex,
540 Twox64Concat,
541 Option<BountyIndex>,
542 T::Consideration,
543 >;
544
545 #[derive(Default)]
547 pub struct SpendContext<Balance> {
548 pub spend_in_context: BTreeMap<Balance, Balance>,
549 }
550
551 #[pallet::call]
552 impl<T: Config<I>, I: 'static> Pallet<T, I> {
553 #[pallet::call_index(0)]
580 #[pallet::weight(<T as Config<I>>::WeightInfo::fund_bounty())]
581 pub fn fund_bounty(
582 origin: OriginFor<T>,
583 asset_kind: Box<T::AssetKind>,
584 #[pallet::compact] value: T::Balance,
585 curator: AccountIdLookupOf<T>,
586 metadata: T::Hash,
587 ) -> DispatchResult {
588 let max_amount = T::SpendOrigin::ensure_origin(origin)?;
589 let curator = T::Lookup::lookup(curator)?;
590 ensure!(T::Preimages::len(&metadata).is_some(), Error::<T, I>::PreimageNotExist);
591
592 let native_amount = T::BalanceConverter::from_asset_balance(value, *asset_kind.clone())
593 .map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
594 ensure!(native_amount >= T::BountyValueMinimum::get(), Error::<T, I>::InvalidValue);
595 ensure!(native_amount <= max_amount, Error::<T, I>::InsufficientPermission);
596
597 with_context::<SpendContext<T::Balance>, _>(|v| {
598 let context = v.or_default();
599 let funding = context.spend_in_context.entry(max_amount).or_default();
600
601 if funding.checked_add(&native_amount).map(|s| s > max_amount).unwrap_or(true) {
602 Err(Error::<T, I>::InsufficientPermission)
603 } else {
604 *funding = funding.saturating_add(native_amount);
605 Ok(())
606 }
607 })
608 .unwrap_or(Ok(()))?;
609
610 let index = BountyCount::<T, I>::get();
611 let payment_status =
612 Self::do_process_funding_payment(index, None, *asset_kind.clone(), value, None)?;
613
614 let bounty = BountyOf::<T, I> {
615 asset_kind: *asset_kind,
616 value,
617 metadata,
618 status: BountyStatus::FundingAttempted { curator, payment_status },
619 };
620 Bounties::<T, I>::insert(index, &bounty);
621 T::Preimages::request(&metadata);
622 BountyCount::<T, I>::put(index + 1);
623
624 Self::deposit_event(Event::<T, I>::BountyCreated { index });
625
626 Ok(())
627 }
628
629 #[pallet::call_index(1)]
654 #[pallet::weight(<T as Config<I>>::WeightInfo::fund_child_bounty())]
655 pub fn fund_child_bounty(
656 origin: OriginFor<T>,
657 #[pallet::compact] parent_bounty_id: BountyIndex,
658 #[pallet::compact] value: T::Balance,
659 metadata: T::Hash,
660 curator: Option<AccountIdLookupOf<T>>,
661 ) -> DispatchResult {
662 let signer = ensure_signed(origin)?;
663 ensure!(T::Preimages::len(&metadata).is_some(), Error::<T, I>::PreimageNotExist);
664
665 let (asset_kind, parent_value, _, _, parent_curator) =
666 Self::get_bounty_details(parent_bounty_id, None)
667 .map_err(|_| Error::<T, I>::InvalidIndex)?;
668 let native_amount = T::BalanceConverter::from_asset_balance(value, asset_kind.clone())
669 .map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
670
671 ensure!(
672 native_amount >= T::ChildBountyValueMinimum::get(),
673 Error::<T, I>::InvalidValue
674 );
675 ensure!(
676 ChildBountiesPerParent::<T, I>::get(parent_bounty_id) <
677 T::MaxActiveChildBountyCount::get(),
678 Error::<T, I>::TooManyChildBounties,
679 );
680
681 let parent_curator = parent_curator.ok_or(Error::<T, I>::UnexpectedStatus)?;
683 let final_curator = match curator {
684 Some(curator) => T::Lookup::lookup(curator)?,
685 None => parent_curator.clone(),
686 };
687 ensure!(signer == parent_curator, Error::<T, I>::RequireCurator);
688
689 let child_bounties_value = ChildBountiesValuePerParent::<T, I>::get(parent_bounty_id);
691 let remaining_parent_value = parent_value.saturating_sub(child_bounties_value);
692 ensure!(remaining_parent_value >= value, Error::<T, I>::InsufficientBountyValue);
693
694 let child_bounty_id = TotalChildBountiesPerParent::<T, I>::get(parent_bounty_id);
696
697 let payment_status = Self::do_process_funding_payment(
699 parent_bounty_id,
700 Some(child_bounty_id),
701 asset_kind,
702 value,
703 None,
704 )?;
705
706 let child_bounty = ChildBounty {
707 parent_bounty: parent_bounty_id,
708 value,
709 metadata,
710 status: BountyStatus::FundingAttempted {
711 curator: final_curator,
712 payment_status: payment_status.clone(),
713 },
714 };
715 ChildBounties::<T, I>::insert(parent_bounty_id, child_bounty_id, child_bounty);
716 T::Preimages::request(&metadata);
717
718 ChildBountiesValuePerParent::<T, I>::mutate(parent_bounty_id, |children_value| {
722 *children_value = children_value.saturating_add(value)
723 });
724
725 ChildBountiesPerParent::<T, I>::mutate(parent_bounty_id, |count| {
727 count.saturating_inc()
728 });
729 TotalChildBountiesPerParent::<T, I>::insert(
730 parent_bounty_id,
731 child_bounty_id.saturating_add(1),
732 );
733
734 Self::deposit_event(Event::<T, I>::ChildBountyCreated {
735 index: parent_bounty_id,
736 child_index: child_bounty_id,
737 });
738
739 Ok(())
740 }
741
742 #[pallet::call_index(2)]
764 #[pallet::weight(match child_bounty_id {
765 None => <T as Config<I>>::WeightInfo::propose_curator_parent_bounty(),
766 Some(_) => <T as Config<I>>::WeightInfo::propose_curator_child_bounty(),
767 })]
768 pub fn propose_curator(
769 origin: OriginFor<T>,
770 #[pallet::compact] parent_bounty_id: BountyIndex,
771 child_bounty_id: Option<BountyIndex>,
772 curator: AccountIdLookupOf<T>,
773 ) -> DispatchResult {
774 let maybe_sender = ensure_signed(origin.clone())
775 .map(Some)
776 .or_else(|_| T::SpendOrigin::ensure_origin(origin.clone()).map(|_| None))?;
777 let curator = T::Lookup::lookup(curator)?;
778
779 let (asset_kind, value, _, status, parent_curator) =
780 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
781 ensure!(status == BountyStatus::CuratorUnassigned, Error::<T, I>::UnexpectedStatus);
782
783 match child_bounty_id {
784 None => {
786 ensure!(maybe_sender.is_none(), BadOrigin);
787 let max_amount = T::SpendOrigin::ensure_origin(origin)?;
788 let native_amount = T::BalanceConverter::from_asset_balance(value, asset_kind)
789 .map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
790 ensure!(native_amount <= max_amount, Error::<T, I>::InsufficientPermission);
791 },
792 Some(_) => {
794 let parent_curator = parent_curator.ok_or(Error::<T, I>::UnexpectedStatus)?;
795 let sender = maybe_sender.ok_or(BadOrigin)?;
796 ensure!(sender == parent_curator, BadOrigin);
797 },
798 };
799
800 let new_status = BountyStatus::Funded { curator: curator.clone() };
801 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
802
803 Self::deposit_event(Event::<T, I>::CuratorProposed {
804 index: parent_bounty_id,
805 child_index: child_bounty_id,
806 curator,
807 });
808
809 Ok(())
810 }
811
812 #[pallet::call_index(3)]
832 #[pallet::weight(<T as Config<I>>::WeightInfo::accept_curator())]
833 pub fn accept_curator(
834 origin: OriginFor<T>,
835 #[pallet::compact] parent_bounty_id: BountyIndex,
836 child_bounty_id: Option<BountyIndex>,
837 ) -> DispatchResult {
838 let signer = ensure_signed(origin)?;
839
840 let (asset_kind, value, _, status, _) =
841 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
842
843 let BountyStatus::Funded { ref curator } = status else {
844 return Err(Error::<T, I>::UnexpectedStatus.into());
845 };
846 ensure!(signer == *curator, Error::<T, I>::RequireCurator);
847
848 let native_amount = T::BalanceConverter::from_asset_balance(value, asset_kind)
849 .map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
850 let curator_deposit = T::Consideration::new(&curator, native_amount)?;
851 CuratorDeposit::<T, I>::insert(parent_bounty_id, child_bounty_id, curator_deposit);
852
853 let new_status = BountyStatus::Active { curator: curator.clone() };
854 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
855
856 Self::deposit_event(Event::<T, I>::BountyBecameActive {
857 index: parent_bounty_id,
858 child_index: child_bounty_id,
859 curator: signer,
860 });
861
862 Ok(())
863 }
864
865 #[pallet::call_index(4)]
891 #[pallet::weight(<T as Config<I>>::WeightInfo::unassign_curator())]
892 pub fn unassign_curator(
893 origin: OriginFor<T>,
894 #[pallet::compact] parent_bounty_id: BountyIndex,
895 child_bounty_id: Option<BountyIndex>,
896 ) -> DispatchResult {
897 let maybe_sender = ensure_signed(origin.clone())
898 .map(Some)
899 .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?;
900
901 let (_, _, _, status, parent_curator) =
902 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
903
904 match status {
905 BountyStatus::Funded { ref curator } => {
906 ensure!(
910 maybe_sender.map_or(true, |sender| {
911 sender == *curator ||
912 parent_curator
913 .map_or(false, |parent_curator| sender == parent_curator)
914 }),
915 BadOrigin
916 );
917 },
918 BountyStatus::Active { ref curator, .. } => {
919 match maybe_sender {
921 None => {
923 if let Some(curator_deposit) =
924 CuratorDeposit::<T, I>::take(parent_bounty_id, child_bounty_id)
925 {
926 T::Consideration::burn(curator_deposit, curator);
927 }
928 },
930 Some(sender) if sender == *curator => {
931 if let Some(curator_deposit) =
932 CuratorDeposit::<T, I>::get(parent_bounty_id, child_bounty_id)
933 {
934 T::Consideration::drop(curator_deposit, curator)?;
937 CuratorDeposit::<T, I>::remove(parent_bounty_id, child_bounty_id);
938 }
939 },
941 Some(sender) => {
942 let parent_curator = parent_curator.ok_or(BadOrigin)?;
943 ensure!(
944 sender == parent_curator && *curator != parent_curator,
945 BadOrigin
946 );
947 if let Some(curator_deposit) =
950 CuratorDeposit::<T, I>::take(parent_bounty_id, child_bounty_id)
951 {
952 T::Consideration::burn(curator_deposit, curator);
953 }
954 },
955 }
956 },
957 _ => return Err(Error::<T, I>::UnexpectedStatus.into()),
958 };
959
960 let new_status = BountyStatus::CuratorUnassigned;
961 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
962
963 Self::deposit_event(Event::<T, I>::CuratorUnassigned {
964 index: parent_bounty_id,
965 child_index: child_bounty_id,
966 });
967
968 Ok(())
969 }
970
971 #[pallet::call_index(5)]
996 #[pallet::weight(<T as Config<I>>::WeightInfo::award_bounty())]
997 pub fn award_bounty(
998 origin: OriginFor<T>,
999 #[pallet::compact] parent_bounty_id: BountyIndex,
1000 child_bounty_id: Option<BountyIndex>,
1001 beneficiary: BeneficiaryLookupOf<T, I>,
1002 ) -> DispatchResult {
1003 let signer = ensure_signed(origin)?;
1004 let beneficiary = T::BeneficiaryLookup::lookup(beneficiary)?;
1005
1006 let (asset_kind, value, _, status, _) =
1007 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
1008
1009 if child_bounty_id.is_none() {
1010 ensure!(
1011 ChildBountiesPerParent::<T, I>::get(parent_bounty_id) == 0,
1012 Error::<T, I>::HasActiveChildBounty
1013 );
1014 }
1015
1016 let BountyStatus::Active { ref curator } = status else {
1017 return Err(Error::<T, I>::UnexpectedStatus.into());
1018 };
1019 ensure!(signer == *curator, Error::<T, I>::RequireCurator);
1020
1021 let beneficiary_payment_status = Self::do_process_payout_payment(
1022 parent_bounty_id,
1023 child_bounty_id,
1024 asset_kind,
1025 value,
1026 beneficiary.clone(),
1027 None,
1028 )?;
1029
1030 let new_status = BountyStatus::PayoutAttempted {
1031 curator: curator.clone(),
1032 beneficiary: beneficiary.clone(),
1033 payment_status: beneficiary_payment_status.clone(),
1034 };
1035 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
1036
1037 Self::deposit_event(Event::<T, I>::BountyAwarded {
1038 index: parent_bounty_id,
1039 child_index: child_bounty_id,
1040 beneficiary,
1041 });
1042
1043 Ok(())
1044 }
1045
1046 #[pallet::call_index(6)]
1070 #[pallet::weight(match child_bounty_id {
1071 None => <T as Config<I>>::WeightInfo::close_parent_bounty(),
1072 Some(_) => <T as Config<I>>::WeightInfo::close_child_bounty(),
1073 })]
1074 pub fn close_bounty(
1075 origin: OriginFor<T>,
1076 #[pallet::compact] parent_bounty_id: BountyIndex,
1077 child_bounty_id: Option<BountyIndex>,
1078 ) -> DispatchResult {
1079 let maybe_sender = ensure_signed(origin.clone())
1080 .map(Some)
1081 .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?;
1082
1083 let (asset_kind, value, _, status, parent_curator) =
1084 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
1085
1086 let maybe_curator = match status {
1087 BountyStatus::Funded { curator } | BountyStatus::Active { curator, .. } => {
1088 Some(curator)
1089 },
1090 BountyStatus::CuratorUnassigned => None,
1091 _ => return Err(Error::<T, I>::UnexpectedStatus.into()),
1092 };
1093
1094 match child_bounty_id {
1095 None => {
1096 ensure!(
1098 ChildBountiesPerParent::<T, I>::get(parent_bounty_id) == 0,
1099 Error::<T, I>::HasActiveChildBounty
1100 );
1101 if let Some(sender) = maybe_sender.as_ref() {
1103 let is_curator =
1104 maybe_curator.as_ref().map_or(false, |curator| curator == sender);
1105 ensure!(is_curator, BadOrigin);
1106 }
1107 },
1108 Some(_) => {
1109 if let Some(sender) = maybe_sender.as_ref() {
1111 let is_curator =
1112 maybe_curator.as_ref().map_or(false, |curator| curator == sender);
1113 let is_parent_curator = parent_curator
1114 .as_ref()
1115 .map_or(false, |parent_curator| parent_curator == sender);
1116 ensure!(is_curator || is_parent_curator, BadOrigin);
1117 }
1118 },
1119 };
1120
1121 let payment_status = Self::do_process_refund_payment(
1122 parent_bounty_id,
1123 child_bounty_id,
1124 asset_kind,
1125 value,
1126 None,
1127 )?;
1128 let new_status = BountyStatus::RefundAttempted {
1129 payment_status: payment_status.clone(),
1130 curator: maybe_curator.clone(),
1131 };
1132 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
1133
1134 Self::deposit_event(Event::<T, I>::BountyCanceled {
1135 index: parent_bounty_id,
1136 child_index: child_bounty_id,
1137 });
1138
1139 Ok(())
1140 }
1141
1142 #[pallet::call_index(7)]
1168 #[pallet::weight(<T as Config<I>>::WeightInfo::check_status_funding().max(
1169 <T as Config<I>>::WeightInfo::check_status_refund(),
1170 ).max(<T as Config<I>>::WeightInfo::check_status_payout()))]
1171 pub fn check_status(
1172 origin: OriginFor<T>,
1173 #[pallet::compact] parent_bounty_id: BountyIndex,
1174 child_bounty_id: Option<BountyIndex>,
1175 ) -> DispatchResultWithPostInfo {
1176 use BountyStatus::*;
1177
1178 ensure_signed(origin)?;
1179 let (asset_kind, value, metadata, status, parent_curator) =
1180 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
1181
1182 let (new_status, weight) = match status {
1183 FundingAttempted { ref payment_status, curator } => {
1184 let new_payment_status = Self::do_check_funding_payment_status(
1185 parent_bounty_id,
1186 child_bounty_id,
1187 payment_status.clone(),
1188 )?;
1189
1190 let new_status = match new_payment_status {
1191 PaymentState::Succeeded => match (child_bounty_id, parent_curator) {
1192 (Some(_), Some(parent_curator)) if curator == parent_curator => {
1193 BountyStatus::Active { curator }
1194 },
1195 _ => BountyStatus::Funded { curator },
1196 },
1197 PaymentState::Pending |
1198 PaymentState::Failed |
1199 PaymentState::Attempted { .. } => BountyStatus::FundingAttempted {
1200 payment_status: new_payment_status,
1201 curator,
1202 },
1203 };
1204
1205 let weight = <T as Config<I>>::WeightInfo::check_status_funding();
1206
1207 (new_status, weight)
1208 },
1209 RefundAttempted { ref payment_status, ref curator } => {
1210 let new_payment_status = Self::do_check_refund_payment_status(
1211 parent_bounty_id,
1212 child_bounty_id,
1213 payment_status.clone(),
1214 )?;
1215
1216 let new_status = match new_payment_status {
1217 PaymentState::Succeeded => {
1218 if let Some(curator) = curator {
1219 if let Some(curator_deposit) =
1223 CuratorDeposit::<T, I>::take(parent_bounty_id, child_bounty_id)
1224 {
1225 T::Consideration::drop(curator_deposit, curator)?;
1226 }
1227 }
1228 if let Some(_) = child_bounty_id {
1229 ChildBountiesValuePerParent::<T, I>::mutate(
1231 parent_bounty_id,
1232 |total_value| *total_value = total_value.saturating_sub(value),
1233 );
1234 }
1235 Self::remove_bounty(parent_bounty_id, child_bounty_id, metadata);
1237 return Ok(Pays::No.into());
1238 },
1239 PaymentState::Pending |
1240 PaymentState::Failed |
1241 PaymentState::Attempted { .. } => BountyStatus::RefundAttempted {
1242 payment_status: new_payment_status,
1243 curator: curator.clone(),
1244 },
1245 };
1246
1247 let weight = <T as Config<I>>::WeightInfo::check_status_refund();
1248
1249 (new_status, weight)
1250 },
1251 PayoutAttempted { ref curator, ref beneficiary, ref payment_status } => {
1252 let new_payment_status = Self::do_check_payout_payment_status(
1253 parent_bounty_id,
1254 child_bounty_id,
1255 asset_kind,
1256 value,
1257 beneficiary.clone(),
1258 payment_status.clone(),
1259 )?;
1260
1261 let new_status = match new_payment_status {
1262 PaymentState::Succeeded => {
1263 if let Some(curator_deposit) =
1264 CuratorDeposit::<T, I>::take(parent_bounty_id, child_bounty_id)
1265 {
1266 T::Consideration::drop(curator_deposit, curator)?;
1270 }
1271 Self::remove_bounty(parent_bounty_id, child_bounty_id, metadata);
1273 return Ok(Pays::No.into());
1274 },
1275 PaymentState::Pending |
1276 PaymentState::Failed |
1277 PaymentState::Attempted { .. } => BountyStatus::PayoutAttempted {
1278 curator: curator.clone(),
1279 beneficiary: beneficiary.clone(),
1280 payment_status: new_payment_status.clone(),
1281 },
1282 };
1283
1284 let weight = <T as Config<I>>::WeightInfo::check_status_payout();
1285
1286 (new_status, weight)
1287 },
1288 _ => return Err(Error::<T, I>::UnexpectedStatus.into()),
1289 };
1290
1291 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
1292
1293 Ok(Some(weight).into())
1294 }
1295
1296 #[pallet::call_index(8)]
1319 #[pallet::weight(<T as Config<I>>::WeightInfo::retry_payment_funding().max(
1320 <T as Config<I>>::WeightInfo::retry_payment_refund(),
1321 ).max(<T as Config<I>>::WeightInfo::retry_payment_payout()))]
1322 pub fn retry_payment(
1323 origin: OriginFor<T>,
1324 #[pallet::compact] parent_bounty_id: BountyIndex,
1325 child_bounty_id: Option<BountyIndex>,
1326 ) -> DispatchResultWithPostInfo {
1327 use BountyStatus::*;
1328
1329 ensure_signed(origin)?;
1330 let (asset_kind, value, _, status, _) =
1331 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
1332
1333 let (new_status, weight) = match status {
1334 FundingAttempted { ref payment_status, ref curator } => {
1335 let new_payment_status = Self::do_process_funding_payment(
1336 parent_bounty_id,
1337 child_bounty_id,
1338 asset_kind,
1339 value,
1340 Some(payment_status.clone()),
1341 )?;
1342
1343 (
1344 FundingAttempted {
1345 payment_status: new_payment_status,
1346 curator: curator.clone(),
1347 },
1348 <T as Config<I>>::WeightInfo::retry_payment_funding(),
1349 )
1350 },
1351 RefundAttempted { ref curator, ref payment_status } => {
1352 let new_payment_status = Self::do_process_refund_payment(
1353 parent_bounty_id,
1354 child_bounty_id,
1355 asset_kind,
1356 value,
1357 Some(payment_status.clone()),
1358 )?;
1359 (
1360 RefundAttempted {
1361 curator: curator.clone(),
1362 payment_status: new_payment_status,
1363 },
1364 <T as Config<I>>::WeightInfo::retry_payment_refund(),
1365 )
1366 },
1367 PayoutAttempted { ref curator, ref beneficiary, ref payment_status } => {
1368 let new_payment_status = Self::do_process_payout_payment(
1369 parent_bounty_id,
1370 child_bounty_id,
1371 asset_kind,
1372 value,
1373 beneficiary.clone(),
1374 Some(payment_status.clone()),
1375 )?;
1376 (
1377 PayoutAttempted {
1378 curator: curator.clone(),
1379 beneficiary: beneficiary.clone(),
1380 payment_status: new_payment_status,
1381 },
1382 <T as Config<I>>::WeightInfo::retry_payment_payout(),
1383 )
1384 },
1385 _ => return Err(Error::<T, I>::UnexpectedStatus.into()),
1386 };
1387
1388 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
1389
1390 Ok(Some(weight).into())
1391 }
1392 }
1393
1394 #[pallet::hooks]
1395 impl<T: Config<I>, I: 'static> Hooks<SystemBlockNumberFor<T>> for Pallet<T, I> {
1396 #[cfg(feature = "try-runtime")]
1397 fn try_state(_n: SystemBlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
1398 Self::do_try_state()
1399 }
1400 }
1401}
1402
1403#[cfg(any(feature = "try-runtime", test))]
1404impl<T: Config<I>, I: 'static> Pallet<T, I> {
1405 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
1409 Self::try_state_bounties_count()?;
1410
1411 for parent_bounty_id in Bounties::<T, I>::iter_keys() {
1412 Self::try_state_child_bounties_count(parent_bounty_id)?;
1413 }
1414
1415 Ok(())
1416 }
1417
1418 fn try_state_bounties_count() -> Result<(), sp_runtime::TryRuntimeError> {
1423 let bounties_length = Bounties::<T, I>::iter().count() as u32;
1424
1425 ensure!(
1426 <BountyCount<T, I>>::get() >= bounties_length,
1427 "`BountyCount` must be grater or equals the number of `Bounties` in storage"
1428 );
1429
1430 Ok(())
1431 }
1432
1433 fn try_state_child_bounties_count(
1438 parent_bounty_id: BountyIndex,
1439 ) -> Result<(), sp_runtime::TryRuntimeError> {
1440 let child_bounties_length =
1441 ChildBounties::<T, I>::iter_prefix(parent_bounty_id).count() as u32;
1442
1443 ensure!(
1444 <ChildBountiesPerParent<T, I>>::get(parent_bounty_id) >= child_bounties_length,
1445 "`ChildBountiesPerParent` must be grater or equals the number of `ChildBounties` in storage"
1446 );
1447
1448 Ok(())
1449 }
1450}
1451
1452impl<T: Config<I>, I: 'static> Pallet<T, I> {
1453 pub fn funding_source_account(
1455 asset_kind: T::AssetKind,
1456 ) -> Result<T::Beneficiary, DispatchError> {
1457 T::FundingSource::try_convert(asset_kind)
1458 .map_err(|_| Error::<T, I>::FailedToConvertSource.into())
1459 }
1460
1461 pub fn bounty_account(
1463 bounty_id: BountyIndex,
1464 asset_kind: T::AssetKind,
1465 ) -> Result<T::Beneficiary, DispatchError> {
1466 T::BountySource::try_convert((bounty_id, asset_kind))
1467 .map_err(|_| Error::<T, I>::FailedToConvertSource.into())
1468 }
1469
1470 pub fn child_bounty_account(
1472 parent_bounty_id: BountyIndex,
1473 child_bounty_id: BountyIndex,
1474 asset_kind: T::AssetKind,
1475 ) -> Result<T::Beneficiary, DispatchError> {
1476 T::ChildBountySource::try_convert((parent_bounty_id, child_bounty_id, asset_kind))
1477 .map_err(|_| Error::<T, I>::FailedToConvertSource.into())
1478 }
1479
1480 pub fn get_bounty_details(
1485 parent_bounty_id: BountyIndex,
1486 child_bounty_id: Option<BountyIndex>,
1487 ) -> Result<
1488 (
1489 T::AssetKind,
1490 T::Balance,
1491 T::Hash,
1492 BountyStatus<T::AccountId, PaymentIdOf<T, I>, T::Beneficiary>,
1493 Option<T::AccountId>,
1494 ),
1495 DispatchError,
1496 > {
1497 let parent_bounty =
1498 Bounties::<T, I>::get(parent_bounty_id).ok_or(Error::<T, I>::InvalidIndex)?;
1499
1500 let parent_curator = if let BountyStatus::Active { curator } = &parent_bounty.status {
1502 Some(curator.clone())
1503 } else {
1504 None
1505 };
1506
1507 match child_bounty_id {
1508 None => Ok((
1509 parent_bounty.asset_kind,
1510 parent_bounty.value,
1511 parent_bounty.metadata,
1512 parent_bounty.status,
1513 parent_curator,
1514 )),
1515 Some(child_bounty_id) => {
1516 let child_bounty = ChildBounties::<T, I>::get(parent_bounty_id, child_bounty_id)
1517 .ok_or(Error::<T, I>::InvalidIndex)?;
1518 Ok((
1519 parent_bounty.asset_kind,
1520 child_bounty.value,
1521 child_bounty.metadata,
1522 child_bounty.status,
1523 parent_curator,
1524 ))
1525 },
1526 }
1527 }
1528
1529 pub fn update_bounty_status(
1531 parent_bounty_id: BountyIndex,
1532 child_bounty_id: Option<BountyIndex>,
1533 new_status: BountyStatus<T::AccountId, PaymentIdOf<T, I>, T::Beneficiary>,
1534 ) -> Result<(), DispatchError> {
1535 match child_bounty_id {
1536 None => {
1537 let mut bounty =
1538 Bounties::<T, I>::get(parent_bounty_id).ok_or(Error::<T, I>::InvalidIndex)?;
1539 bounty.status = new_status;
1540 Bounties::<T, I>::insert(parent_bounty_id, bounty);
1541 },
1542 Some(child_bounty_id) => {
1543 let mut bounty = ChildBounties::<T, I>::get(parent_bounty_id, child_bounty_id)
1544 .ok_or(Error::<T, I>::InvalidIndex)?;
1545 bounty.status = new_status;
1546 ChildBounties::<T, I>::insert(parent_bounty_id, child_bounty_id, bounty);
1547 },
1548 }
1549
1550 Ok(())
1551 }
1552
1553 fn calculate_payout(
1555 parent_bounty_id: BountyIndex,
1556 child_bounty_id: Option<BountyIndex>,
1557 value: T::Balance,
1558 ) -> T::Balance {
1559 match child_bounty_id {
1560 None => {
1561 let children_value = ChildBountiesValuePerParent::<T, I>::get(parent_bounty_id);
1564 debug_assert!(children_value <= value);
1565 let payout = value.saturating_sub(children_value);
1566 payout
1567 },
1568 Some(_) => value,
1569 }
1570 }
1571
1572 fn remove_bounty(
1574 parent_bounty_id: BountyIndex,
1575 child_bounty_id: Option<BountyIndex>,
1576 metadata: T::Hash,
1577 ) {
1578 match child_bounty_id {
1579 None => {
1580 Bounties::<T, I>::remove(parent_bounty_id);
1581 ChildBountiesPerParent::<T, I>::remove(parent_bounty_id);
1582 TotalChildBountiesPerParent::<T, I>::remove(parent_bounty_id);
1583 ChildBountiesValuePerParent::<T, I>::remove(parent_bounty_id);
1584 },
1585 Some(child_bounty_id) => {
1586 ChildBounties::<T, I>::remove(parent_bounty_id, child_bounty_id);
1587 ChildBountiesPerParent::<T, I>::mutate(parent_bounty_id, |count| {
1588 count.saturating_dec()
1589 });
1590 },
1591 }
1592
1593 T::Preimages::unrequest(&metadata);
1594 }
1595
1596 fn do_process_funding_payment(
1598 parent_bounty_id: BountyIndex,
1599 child_bounty_id: Option<BountyIndex>,
1600 asset_kind: T::AssetKind,
1601 value: T::Balance,
1602 maybe_payment_status: Option<PaymentState<PaymentIdOf<T, I>>>,
1603 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1604 if let Some(payment_status) = maybe_payment_status {
1605 ensure!(payment_status.is_pending_or_failed(), Error::<T, I>::UnexpectedStatus);
1606 }
1607
1608 let (source, beneficiary) = match child_bounty_id {
1609 None => (
1610 Self::funding_source_account(asset_kind.clone())?,
1611 Self::bounty_account(parent_bounty_id, asset_kind.clone())?,
1612 ),
1613 Some(child_bounty_id) => (
1614 Self::bounty_account(parent_bounty_id, asset_kind.clone())?,
1615 Self::child_bounty_account(parent_bounty_id, child_bounty_id, asset_kind.clone())?,
1616 ),
1617 };
1618
1619 let id = <T as Config<I>>::Paymaster::pay(&source, &beneficiary, asset_kind, value)
1620 .map_err(|_| Error::<T, I>::FundingError)?;
1621
1622 Self::deposit_event(Event::<T, I>::Paid {
1623 index: parent_bounty_id,
1624 child_index: child_bounty_id,
1625 payment_id: id,
1626 });
1627
1628 Ok(PaymentState::Attempted { id })
1629 }
1630
1631 fn do_check_funding_payment_status(
1634 parent_bounty_id: BountyIndex,
1635 child_bounty_id: Option<BountyIndex>,
1636 payment_status: PaymentState<PaymentIdOf<T, I>>,
1637 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1638 let payment_id = payment_status.get_attempt_id().ok_or(Error::<T, I>::UnexpectedStatus)?;
1639
1640 match <T as Config<I>>::Paymaster::check_payment(payment_id) {
1641 PaymentStatus::Success => {
1642 Self::deposit_event(Event::<T, I>::BountyFundingProcessed {
1643 index: parent_bounty_id,
1644 child_index: child_bounty_id,
1645 });
1646 Ok(PaymentState::Succeeded)
1647 },
1648 PaymentStatus::InProgress | PaymentStatus::Unknown => {
1649 return Err(Error::<T, I>::FundingInconclusive.into())
1650 },
1651 PaymentStatus::Failure => {
1652 Self::deposit_event(Event::<T, I>::PaymentFailed {
1653 index: parent_bounty_id,
1654 child_index: child_bounty_id,
1655 payment_id,
1656 });
1657 return Ok(PaymentState::Failed);
1658 },
1659 }
1660 }
1661
1662 fn do_process_refund_payment(
1665 parent_bounty_id: BountyIndex,
1666 child_bounty_id: Option<BountyIndex>,
1667 asset_kind: T::AssetKind,
1668 value: T::Balance,
1669 payment_status: Option<PaymentState<PaymentIdOf<T, I>>>,
1670 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1671 if let Some(payment_status) = payment_status {
1672 ensure!(payment_status.is_pending_or_failed(), Error::<T, I>::UnexpectedStatus);
1673 }
1674
1675 let (source, beneficiary) = match child_bounty_id {
1676 None => (
1677 Self::bounty_account(parent_bounty_id, asset_kind.clone())?,
1678 Self::funding_source_account(asset_kind.clone())?,
1679 ),
1680 Some(child_bounty_id) => (
1681 Self::child_bounty_account(parent_bounty_id, child_bounty_id, asset_kind.clone())?,
1682 Self::bounty_account(parent_bounty_id, asset_kind.clone())?,
1683 ),
1684 };
1685
1686 let id = <T as Config<I>>::Paymaster::pay(&source, &beneficiary, asset_kind, value)
1687 .map_err(|_| Error::<T, I>::RefundError)?;
1688
1689 Self::deposit_event(Event::<T, I>::Paid {
1690 index: parent_bounty_id,
1691 child_index: child_bounty_id,
1692 payment_id: id,
1693 });
1694
1695 Ok(PaymentState::Attempted { id })
1696 }
1697
1698 fn do_check_refund_payment_status(
1701 parent_bounty_id: BountyIndex,
1702 child_bounty_id: Option<BountyIndex>,
1703 payment_status: PaymentState<PaymentIdOf<T, I>>,
1704 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1705 let payment_id = payment_status.get_attempt_id().ok_or(Error::<T, I>::UnexpectedStatus)?;
1706
1707 match <T as pallet::Config<I>>::Paymaster::check_payment(payment_id) {
1708 PaymentStatus::Success => {
1709 Self::deposit_event(Event::<T, I>::BountyRefundProcessed {
1710 index: parent_bounty_id,
1711 child_index: child_bounty_id,
1712 });
1713 Ok(PaymentState::Succeeded)
1714 },
1715 PaymentStatus::InProgress | PaymentStatus::Unknown =>
1716 {
1718 Err(Error::<T, I>::RefundInconclusive.into())
1719 },
1720 PaymentStatus::Failure => {
1721 Self::deposit_event(Event::<T, I>::PaymentFailed {
1723 index: parent_bounty_id,
1724 child_index: child_bounty_id,
1725 payment_id,
1726 });
1727 Ok(PaymentState::Failed)
1728 },
1729 }
1730 }
1731
1732 fn do_process_payout_payment(
1734 parent_bounty_id: BountyIndex,
1735 child_bounty_id: Option<BountyIndex>,
1736 asset_kind: T::AssetKind,
1737 value: T::Balance,
1738 beneficiary: T::Beneficiary,
1739 payment_status: Option<PaymentState<PaymentIdOf<T, I>>>,
1740 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1741 if let Some(payment_status) = payment_status {
1742 ensure!(payment_status.is_pending_or_failed(), Error::<T, I>::UnexpectedStatus);
1743 }
1744
1745 let payout = Self::calculate_payout(parent_bounty_id, child_bounty_id, value);
1746
1747 let source = match child_bounty_id {
1748 None => Self::bounty_account(parent_bounty_id, asset_kind.clone())?,
1749 Some(child_bounty_id) => {
1750 Self::child_bounty_account(parent_bounty_id, child_bounty_id, asset_kind.clone())?
1751 },
1752 };
1753
1754 let id = <T as Config<I>>::Paymaster::pay(&source, &beneficiary, asset_kind, payout)
1755 .map_err(|_| Error::<T, I>::PayoutError)?;
1756
1757 Self::deposit_event(Event::<T, I>::Paid {
1758 index: parent_bounty_id,
1759 child_index: child_bounty_id,
1760 payment_id: id,
1761 });
1762
1763 Ok(PaymentState::Attempted { id })
1764 }
1765
1766 fn do_check_payout_payment_status(
1769 parent_bounty_id: BountyIndex,
1770 child_bounty_id: Option<BountyIndex>,
1771 asset_kind: T::AssetKind,
1772 value: T::Balance,
1773 beneficiary: T::Beneficiary,
1774 payment_status: PaymentState<PaymentIdOf<T, I>>,
1775 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1776 let payment_id = payment_status.get_attempt_id().ok_or(Error::<T, I>::UnexpectedStatus)?;
1777
1778 match <T as pallet::Config<I>>::Paymaster::check_payment(payment_id) {
1779 PaymentStatus::Success => {
1780 let payout = Self::calculate_payout(parent_bounty_id, child_bounty_id, value);
1781
1782 Self::deposit_event(Event::<T, I>::BountyPayoutProcessed {
1783 index: parent_bounty_id,
1784 child_index: child_bounty_id,
1785 asset_kind: asset_kind.clone(),
1786 value: payout,
1787 beneficiary,
1788 });
1789
1790 Ok(PaymentState::Succeeded)
1791 },
1792 PaymentStatus::InProgress | PaymentStatus::Unknown =>
1793 {
1795 Err(Error::<T, I>::PayoutInconclusive.into())
1796 },
1797 PaymentStatus::Failure => {
1798 Self::deposit_event(Event::<T, I>::PaymentFailed {
1800 index: parent_bounty_id,
1801 child_index: child_bounty_id,
1802 payment_id,
1803 });
1804 Ok(PaymentState::Failed)
1805 },
1806 }
1807 }
1808}
1809
1810pub struct CuratorDepositAmount<Mult, Min, Max, Balance>(PhantomData<(Mult, Min, Max, Balance)>);
1815impl<Mult, Min, Max, Balance> Convert<Balance, Balance>
1816 for CuratorDepositAmount<Mult, Min, Max, Balance>
1817where
1818 Balance: frame_support::traits::tokens::Balance,
1819 Min: Get<Option<Balance>>,
1820 Max: Get<Option<Balance>>,
1821 Mult: Get<Permill>,
1822{
1823 fn convert(value: Balance) -> Balance {
1824 let mut deposit = Mult::get().mul_floor(value);
1825
1826 if let Some(min) = Min::get() {
1827 if deposit < min {
1828 deposit = min;
1829 }
1830 }
1831
1832 if let Some(max) = Max::get() {
1833 if deposit > max {
1834 deposit = max;
1835 }
1836 }
1837
1838 deposit
1839 }
1840}
1841
1842pub struct PalletIdAsFundingSource<Id, T, C, I = ()>(PhantomData<(Id, T, C, I)>);
1853impl<Id, T, C, I> TryConvert<T::AssetKind, T::Beneficiary> for PalletIdAsFundingSource<Id, T, C, I>
1854where
1855 Id: Get<PalletId>,
1856 T: crate::Config<I>,
1857 C: Convert<T::AccountId, T::Beneficiary>,
1858{
1859 fn try_convert(_asset_kind: T::AssetKind) -> Result<T::Beneficiary, T::AssetKind> {
1860 let account: T::AccountId = Id::get().into_account_truncating();
1861 Ok(C::convert(account))
1862 }
1863}
1864
1865pub struct BountyAccountPrefix;
1870impl Get<[u8; 3]> for BountyAccountPrefix {
1871 fn get() -> [u8; 3] {
1872 *b"mbt"
1873 }
1874}
1875
1876pub struct ChildBountyAccountPrefix;
1881impl Get<[u8; 3]> for ChildBountyAccountPrefix {
1882 fn get() -> [u8; 3] {
1883 *b"mcb"
1884 }
1885}
1886
1887pub struct BountySourceFromPalletId<Id, Prefix, T, C, I = ()>(PhantomData<(Id, Prefix, T, C, I)>);
1906impl<Id, Prefix, T, C, I> TryConvert<(BountyIndex, T::AssetKind), T::Beneficiary>
1907 for BountySourceFromPalletId<Id, Prefix, T, C, I>
1908where
1909 Id: Get<PalletId>,
1910 Prefix: Get<[u8; 3]>,
1911 T: crate::Config<I>,
1912 C: Convert<T::AccountId, T::Beneficiary>,
1913{
1914 fn try_convert(
1915 (parent_bounty_id, _asset_kind): (BountyIndex, T::AssetKind),
1916 ) -> Result<T::Beneficiary, (BountyIndex, T::AssetKind)> {
1917 let account: T::AccountId =
1918 Id::get().into_sub_account_truncating((Prefix::get(), parent_bounty_id));
1919 Ok(C::convert(account))
1920 }
1921}
1922
1923pub struct ChildBountySourceFromPalletId<Id, Prefix, T, C, I = ()>(
1943 PhantomData<(Id, Prefix, T, C, I)>,
1944);
1945impl<Id, Prefix, T, C, I> TryConvert<(BountyIndex, BountyIndex, T::AssetKind), T::Beneficiary>
1946 for ChildBountySourceFromPalletId<Id, Prefix, T, C, I>
1947where
1948 Id: Get<PalletId>,
1949 Prefix: Get<[u8; 3]>,
1950 T: crate::Config<I>,
1951 C: Convert<T::AccountId, T::Beneficiary>,
1952{
1953 fn try_convert(
1954 (parent_bounty_id, child_bounty_id, _asset_kind): (BountyIndex, BountyIndex, T::AssetKind),
1955 ) -> Result<T::Beneficiary, (BountyIndex, BountyIndex, T::AssetKind)> {
1956 let account: T::AccountId = Id::get().into_sub_account_truncating((
1959 Prefix::get(),
1960 parent_bounty_id,
1961 child_bounty_id,
1962 ));
1963 Ok(C::convert(account))
1964 }
1965}