1#![doc = docify::embed!("src/tests.rs", fund_bounty_works)]
61#![doc = docify::embed!("src/tests.rs", award_bounty_works)]
64#![cfg_attr(not(feature = "std"), no_std)]
71
72mod benchmarking;
73mod mock;
74mod tests;
75pub mod weights;
76#[cfg(feature = "runtime-benchmarks")]
77pub use benchmarking::ArgumentsFactory;
78pub use pallet::*;
79pub use weights::WeightInfo;
80
81extern crate alloc;
82use alloc::{boxed::Box, collections::btree_map::BTreeMap};
83use frame_support::{
84 dispatch::{DispatchResult, DispatchResultWithPostInfo},
85 dispatch_context::with_context,
86 pallet_prelude::*,
87 traits::{
88 tokens::{
89 Balance, ConversionFromAssetBalance, ConversionToAssetBalance, PayWithSource,
90 PaymentStatus,
91 },
92 Consideration, EnsureOrigin, Get, QueryPreimage, StorePreimage,
93 },
94 PalletId,
95};
96use frame_system::pallet_prelude::{
97 ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor,
98};
99use scale_info::TypeInfo;
100use sp_runtime::{
101 traits::{AccountIdConversion, BadOrigin, Convert, Saturating, StaticLookup, TryConvert, Zero},
102 Debug, Permill,
103};
104
105pub type BeneficiaryLookupOf<T, I> = <<T as Config<I>>::BeneficiaryLookup as StaticLookup>::Source;
107pub type BountyIndex = u32;
109pub type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
111pub type PaymentIdOf<T, I = ()> = <<T as crate::Config<I>>::Paymaster as PayWithSource>::Id;
113pub type BountyOf<T, I> = Bounty<
115 <T as frame_system::Config>::AccountId,
116 <T as Config<I>>::Balance,
117 <T as Config<I>>::AssetKind,
118 <T as frame_system::Config>::Hash,
119 PaymentIdOf<T, I>,
120 <T as Config<I>>::Beneficiary,
121>;
122pub type ChildBountyOf<T, I> = ChildBounty<
124 <T as frame_system::Config>::AccountId,
125 <T as Config<I>>::Balance,
126 <T as frame_system::Config>::Hash,
127 PaymentIdOf<T, I>,
128 <T as Config<I>>::Beneficiary,
129>;
130
131#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
133pub struct Bounty<AccountId, Balance, AssetKind, Hash, PaymentId, Beneficiary> {
134 pub asset_kind: AssetKind,
136 pub value: Balance,
141 pub metadata: Hash,
146 pub status: BountyStatus<AccountId, PaymentId, Beneficiary>,
148}
149
150#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
152pub struct ChildBounty<AccountId, Balance, Hash, PaymentId, Beneficiary> {
153 pub parent_bounty: BountyIndex,
155 pub value: Balance,
159 pub metadata: Hash,
164 pub status: BountyStatus<AccountId, PaymentId, Beneficiary>,
166}
167
168#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
170pub enum BountyStatus<AccountId, PaymentId, Beneficiary> {
171 FundingAttempted {
178 curator: AccountId,
180 payment_status: PaymentState<PaymentId>,
183 },
184 Funded {
186 curator: AccountId,
188 },
189 CuratorUnassigned,
193 Active {
198 curator: AccountId,
200 },
201 RefundAttempted {
206 curator: Option<AccountId>,
210 payment_status: PaymentState<PaymentId>,
213 },
214 PayoutAttempted {
220 curator: AccountId,
222 beneficiary: Beneficiary,
224 payment_status: PaymentState<PaymentId>,
226 },
227}
228
229#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, Debug, TypeInfo)]
235pub enum PaymentState<Id> {
236 Pending,
238 Attempted { id: Id },
240 Failed,
242 Succeeded,
244}
245impl<Id: Clone> PaymentState<Id> {
246 pub fn is_pending_or_failed(&self) -> bool {
248 matches!(self, PaymentState::Pending | PaymentState::Failed)
249 }
250
251 pub fn get_attempt_id(&self) -> Option<Id> {
254 match self {
255 PaymentState::Attempted { id } => Some(id.clone()),
256 _ => None,
257 }
258 }
259}
260
261#[frame_support::pallet]
262pub mod pallet {
263 use super::*;
264
265 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
266
267 #[pallet::pallet]
268 #[pallet::storage_version(STORAGE_VERSION)]
269 pub struct Pallet<T, I = ()>(_);
270
271 #[pallet::config]
272 pub trait Config<I: 'static = ()>: frame_system::Config {
273 type Balance: Balance;
275
276 type RejectOrigin: EnsureOrigin<Self::RuntimeOrigin>;
278
279 type SpendOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::Balance>;
282
283 type AssetKind: Parameter + MaxEncodedLen;
286
287 type Beneficiary: Parameter + MaxEncodedLen;
289
290 type BeneficiaryLookup: StaticLookup<Target = Self::Beneficiary>;
292
293 #[pallet::constant]
295 type BountyValueMinimum: Get<Self::Balance>;
296
297 #[pallet::constant]
299 type ChildBountyValueMinimum: Get<Self::Balance>;
300
301 #[pallet::constant]
303 type MaxActiveChildBountyCount: Get<u32>;
304
305 type WeightInfo: WeightInfo;
307
308 type FundingSource: TryConvert<
312 Self::AssetKind,
313 <<Self as pallet::Config<I>>::Paymaster as PayWithSource>::Source,
314 >;
315
316 type BountySource: TryConvert<
320 (BountyIndex, Self::AssetKind),
321 <<Self as pallet::Config<I>>::Paymaster as PayWithSource>::Source,
322 >;
323
324 type ChildBountySource: TryConvert<
330 (BountyIndex, BountyIndex, Self::AssetKind),
331 <<Self as pallet::Config<I>>::Paymaster as PayWithSource>::Source,
332 >;
333
334 type Paymaster: PayWithSource<
337 Balance = Self::Balance,
338 Source = Self::Beneficiary,
339 Beneficiary = Self::Beneficiary,
340 AssetKind = Self::AssetKind,
341 >;
342
343 type BalanceConverter: ConversionFromAssetBalance<Self::Balance, Self::AssetKind, Self::Balance>
350 + ConversionToAssetBalance<Self::Balance, Self::AssetKind, Self::Balance>;
351
352 type Preimages: QueryPreimage<H = Self::Hashing> + StorePreimage;
354
355 type Consideration: Consideration<Self::AccountId, Self::Balance>;
365
366 #[cfg(feature = "runtime-benchmarks")]
368 type BenchmarkHelper: benchmarking::ArgumentsFactory<
369 Self::AssetKind,
370 Self::Beneficiary,
371 Self::Balance,
372 >;
373 }
374
375 #[pallet::error]
376 pub enum Error<T, I = ()> {
377 InvalidIndex,
379 ReasonTooBig,
381 InvalidValue,
383 FailedToConvertBalance,
386 UnexpectedStatus,
388 RequireCurator,
390 InsufficientPermission,
393 FundingError,
395 RefundError,
397 PayoutError,
399 FundingInconclusive,
401 RefundInconclusive,
403 PayoutInconclusive,
405 FailedToConvertSource,
408 HasActiveChildBounty,
410 TooManyChildBounties,
412 InsufficientBountyValue,
414 PreimageNotExist,
416 }
417
418 #[pallet::event]
419 #[pallet::generate_deposit(pub(super) fn deposit_event)]
420 pub enum Event<T: Config<I>, I: 'static = ()> {
421 BountyCreated { index: BountyIndex },
423 ChildBountyCreated { index: BountyIndex, child_index: BountyIndex },
425 BountyBecameActive {
427 index: BountyIndex,
428 child_index: Option<BountyIndex>,
429 curator: T::AccountId,
430 },
431 BountyAwarded {
433 index: BountyIndex,
434 child_index: Option<BountyIndex>,
435 beneficiary: T::Beneficiary,
436 },
437 BountyPayoutProcessed {
439 index: BountyIndex,
440 child_index: Option<BountyIndex>,
441 asset_kind: T::AssetKind,
442 value: T::Balance,
443 beneficiary: T::Beneficiary,
444 },
445 BountyFundingProcessed { index: BountyIndex, child_index: Option<BountyIndex> },
447 BountyRefundProcessed { index: BountyIndex, child_index: Option<BountyIndex> },
449 BountyCanceled { index: BountyIndex, child_index: Option<BountyIndex> },
451 CuratorUnassigned { index: BountyIndex, child_index: Option<BountyIndex> },
453 CuratorProposed {
455 index: BountyIndex,
456 child_index: Option<BountyIndex>,
457 curator: T::AccountId,
458 },
459 PaymentFailed {
461 index: BountyIndex,
462 child_index: Option<BountyIndex>,
463 payment_id: PaymentIdOf<T, I>,
464 },
465 Paid { index: BountyIndex, child_index: Option<BountyIndex>, payment_id: PaymentIdOf<T, I> },
467 }
468
469 #[pallet::composite_enum]
471 pub enum HoldReason<I: 'static = ()> {
472 #[codec(index = 0)]
474 CuratorDeposit,
475 }
476
477 #[pallet::storage]
479 pub type BountyCount<T: Config<I>, I: 'static = ()> = StorageValue<_, u32, ValueQuery>;
480
481 #[pallet::storage]
483 pub type Bounties<T: Config<I>, I: 'static = ()> =
484 StorageMap<_, Twox64Concat, BountyIndex, BountyOf<T, I>>;
485
486 #[pallet::storage]
490 pub type ChildBounties<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
491 _,
492 Twox64Concat,
493 BountyIndex,
494 Twox64Concat,
495 BountyIndex,
496 ChildBountyOf<T, I>,
497 >;
498
499 #[pallet::storage]
503 pub type ChildBountiesPerParent<T: Config<I>, I: 'static = ()> =
504 StorageMap<_, Twox64Concat, BountyIndex, u32, ValueQuery>;
505
506 #[pallet::storage]
510 pub type TotalChildBountiesPerParent<T: Config<I>, I: 'static = ()> =
511 StorageMap<_, Twox64Concat, BountyIndex, u32, ValueQuery>;
512
513 #[pallet::storage]
518 pub type ChildBountiesValuePerParent<T: Config<I>, I: 'static = ()> =
519 StorageMap<_, Twox64Concat, BountyIndex, T::Balance, ValueQuery>;
520
521 #[pallet::storage]
533 pub type CuratorDeposit<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
534 _,
535 Twox64Concat,
536 BountyIndex,
537 Twox64Concat,
538 Option<BountyIndex>,
539 T::Consideration,
540 >;
541
542 #[derive(Default)]
544 pub struct SpendContext<Balance> {
545 pub spend_in_context: BTreeMap<Balance, Balance>,
546 }
547
548 #[pallet::call]
549 impl<T: Config<I>, I: 'static> Pallet<T, I> {
550 #[pallet::call_index(0)]
577 #[pallet::weight(<T as Config<I>>::WeightInfo::fund_bounty())]
578 pub fn fund_bounty(
579 origin: OriginFor<T>,
580 asset_kind: Box<T::AssetKind>,
581 #[pallet::compact] value: T::Balance,
582 curator: AccountIdLookupOf<T>,
583 metadata: T::Hash,
584 ) -> DispatchResult {
585 let max_amount = T::SpendOrigin::ensure_origin(origin)?;
586 let curator = T::Lookup::lookup(curator)?;
587 ensure!(T::Preimages::len(&metadata).is_some(), Error::<T, I>::PreimageNotExist);
588
589 let native_amount = T::BalanceConverter::from_asset_balance(value, *asset_kind.clone())
590 .map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
591 ensure!(native_amount >= T::BountyValueMinimum::get(), Error::<T, I>::InvalidValue);
592 ensure!(native_amount <= max_amount, Error::<T, I>::InsufficientPermission);
593
594 with_context::<SpendContext<T::Balance>, _>(|v| {
595 let context = v.or_default();
596 let funding = context.spend_in_context.entry(max_amount).or_default();
597
598 if funding.checked_add(&native_amount).map(|s| s > max_amount).unwrap_or(true) {
599 Err(Error::<T, I>::InsufficientPermission)
600 } else {
601 *funding = funding.saturating_add(native_amount);
602 Ok(())
603 }
604 })
605 .unwrap_or(Ok(()))?;
606
607 let index = BountyCount::<T, I>::get();
608 let payment_status =
609 Self::do_process_funding_payment(index, None, *asset_kind.clone(), value, None)?;
610
611 let bounty = BountyOf::<T, I> {
612 asset_kind: *asset_kind,
613 value,
614 metadata,
615 status: BountyStatus::FundingAttempted { curator, payment_status },
616 };
617 Bounties::<T, I>::insert(index, &bounty);
618 T::Preimages::request(&metadata);
619 BountyCount::<T, I>::put(index + 1);
620
621 Self::deposit_event(Event::<T, I>::BountyCreated { index });
622
623 Ok(())
624 }
625
626 #[pallet::call_index(1)]
651 #[pallet::weight(<T as Config<I>>::WeightInfo::fund_child_bounty())]
652 pub fn fund_child_bounty(
653 origin: OriginFor<T>,
654 #[pallet::compact] parent_bounty_id: BountyIndex,
655 #[pallet::compact] value: T::Balance,
656 metadata: T::Hash,
657 curator: Option<AccountIdLookupOf<T>>,
658 ) -> DispatchResult {
659 let signer = ensure_signed(origin)?;
660 ensure!(T::Preimages::len(&metadata).is_some(), Error::<T, I>::PreimageNotExist);
661
662 let (asset_kind, parent_value, _, _, parent_curator) =
663 Self::get_bounty_details(parent_bounty_id, None)
664 .map_err(|_| Error::<T, I>::InvalidIndex)?;
665 let native_amount = T::BalanceConverter::from_asset_balance(value, asset_kind.clone())
666 .map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
667
668 ensure!(
669 native_amount >= T::ChildBountyValueMinimum::get(),
670 Error::<T, I>::InvalidValue
671 );
672 ensure!(
673 ChildBountiesPerParent::<T, I>::get(parent_bounty_id) <
674 T::MaxActiveChildBountyCount::get(),
675 Error::<T, I>::TooManyChildBounties,
676 );
677
678 let parent_curator = parent_curator.ok_or(Error::<T, I>::UnexpectedStatus)?;
680 let final_curator = match curator {
681 Some(curator) => T::Lookup::lookup(curator)?,
682 None => parent_curator.clone(),
683 };
684 ensure!(signer == parent_curator, Error::<T, I>::RequireCurator);
685
686 let child_bounties_value = ChildBountiesValuePerParent::<T, I>::get(parent_bounty_id);
688 let remaining_parent_value = parent_value.saturating_sub(child_bounties_value);
689 ensure!(remaining_parent_value >= value, Error::<T, I>::InsufficientBountyValue);
690
691 let child_bounty_id = TotalChildBountiesPerParent::<T, I>::get(parent_bounty_id);
693
694 let payment_status = Self::do_process_funding_payment(
696 parent_bounty_id,
697 Some(child_bounty_id),
698 asset_kind,
699 value,
700 None,
701 )?;
702
703 let child_bounty = ChildBounty {
704 parent_bounty: parent_bounty_id,
705 value,
706 metadata,
707 status: BountyStatus::FundingAttempted {
708 curator: final_curator,
709 payment_status: payment_status.clone(),
710 },
711 };
712 ChildBounties::<T, I>::insert(parent_bounty_id, child_bounty_id, child_bounty);
713 T::Preimages::request(&metadata);
714
715 ChildBountiesValuePerParent::<T, I>::mutate(parent_bounty_id, |children_value| {
719 *children_value = children_value.saturating_add(value)
720 });
721
722 ChildBountiesPerParent::<T, I>::mutate(parent_bounty_id, |count| {
724 count.saturating_inc()
725 });
726 TotalChildBountiesPerParent::<T, I>::insert(
727 parent_bounty_id,
728 child_bounty_id.saturating_add(1),
729 );
730
731 Self::deposit_event(Event::<T, I>::ChildBountyCreated {
732 index: parent_bounty_id,
733 child_index: child_bounty_id,
734 });
735
736 Ok(())
737 }
738
739 #[pallet::call_index(2)]
761 #[pallet::weight(match child_bounty_id {
762 None => <T as Config<I>>::WeightInfo::propose_curator_parent_bounty(),
763 Some(_) => <T as Config<I>>::WeightInfo::propose_curator_child_bounty(),
764 })]
765 pub fn propose_curator(
766 origin: OriginFor<T>,
767 #[pallet::compact] parent_bounty_id: BountyIndex,
768 child_bounty_id: Option<BountyIndex>,
769 curator: AccountIdLookupOf<T>,
770 ) -> DispatchResult {
771 let maybe_sender = ensure_signed(origin.clone())
772 .map(Some)
773 .or_else(|_| T::SpendOrigin::ensure_origin(origin.clone()).map(|_| None))?;
774 let curator = T::Lookup::lookup(curator)?;
775
776 let (asset_kind, value, _, status, parent_curator) =
777 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
778 ensure!(status == BountyStatus::CuratorUnassigned, Error::<T, I>::UnexpectedStatus);
779
780 match child_bounty_id {
781 None => {
783 ensure!(maybe_sender.is_none(), BadOrigin);
784 let max_amount = T::SpendOrigin::ensure_origin(origin)?;
785 let native_amount = T::BalanceConverter::from_asset_balance(value, asset_kind)
786 .map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
787 ensure!(native_amount <= max_amount, Error::<T, I>::InsufficientPermission);
788 },
789 Some(_) => {
791 let parent_curator = parent_curator.ok_or(Error::<T, I>::UnexpectedStatus)?;
792 let sender = maybe_sender.ok_or(BadOrigin)?;
793 ensure!(sender == parent_curator, BadOrigin);
794 },
795 };
796
797 let new_status = BountyStatus::Funded { curator: curator.clone() };
798 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
799
800 Self::deposit_event(Event::<T, I>::CuratorProposed {
801 index: parent_bounty_id,
802 child_index: child_bounty_id,
803 curator,
804 });
805
806 Ok(())
807 }
808
809 #[pallet::call_index(3)]
829 #[pallet::weight(<T as Config<I>>::WeightInfo::accept_curator())]
830 pub fn accept_curator(
831 origin: OriginFor<T>,
832 #[pallet::compact] parent_bounty_id: BountyIndex,
833 child_bounty_id: Option<BountyIndex>,
834 ) -> DispatchResult {
835 let signer = ensure_signed(origin)?;
836
837 let (asset_kind, value, _, status, _) =
838 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
839
840 let BountyStatus::Funded { ref curator } = status else {
841 return Err(Error::<T, I>::UnexpectedStatus.into())
842 };
843 ensure!(signer == *curator, Error::<T, I>::RequireCurator);
844
845 let native_amount = T::BalanceConverter::from_asset_balance(value, asset_kind)
846 .map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
847 let curator_deposit = T::Consideration::new(&curator, native_amount)?;
848 CuratorDeposit::<T, I>::insert(parent_bounty_id, child_bounty_id, curator_deposit);
849
850 let new_status = BountyStatus::Active { curator: curator.clone() };
851 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
852
853 Self::deposit_event(Event::<T, I>::BountyBecameActive {
854 index: parent_bounty_id,
855 child_index: child_bounty_id,
856 curator: signer,
857 });
858
859 Ok(())
860 }
861
862 #[pallet::call_index(4)]
888 #[pallet::weight(<T as Config<I>>::WeightInfo::unassign_curator())]
889 pub fn unassign_curator(
890 origin: OriginFor<T>,
891 #[pallet::compact] parent_bounty_id: BountyIndex,
892 child_bounty_id: Option<BountyIndex>,
893 ) -> DispatchResult {
894 let maybe_sender = ensure_signed(origin.clone())
895 .map(Some)
896 .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?;
897
898 let (_, _, _, status, parent_curator) =
899 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
900
901 match status {
902 BountyStatus::Funded { ref curator } => {
903 ensure!(
907 maybe_sender.map_or(true, |sender| {
908 sender == *curator ||
909 parent_curator
910 .map_or(false, |parent_curator| sender == parent_curator)
911 }),
912 BadOrigin
913 );
914 },
915 BountyStatus::Active { ref curator, .. } => {
916 let maybe_curator_deposit =
917 CuratorDeposit::<T, I>::take(parent_bounty_id, child_bounty_id);
918 match maybe_sender {
920 None => {
922 if let Some(curator_deposit) = maybe_curator_deposit {
923 T::Consideration::burn(curator_deposit, curator);
924 }
925 },
927 Some(sender) if sender == *curator => {
928 if let Some(curator_deposit) = maybe_curator_deposit {
929 T::Consideration::drop(curator_deposit, curator)?;
932 }
933 },
935 Some(sender) => {
936 if let Some(parent_curator) = parent_curator {
937 if sender == parent_curator && *curator != parent_curator {
940 if let Some(curator_deposit) = maybe_curator_deposit {
941 T::Consideration::burn(curator_deposit, curator);
942 }
943 } else {
944 return Err(BadOrigin.into());
945 }
946 }
947 },
948 }
949 },
950 _ => return Err(Error::<T, I>::UnexpectedStatus.into()),
951 };
952
953 let new_status = BountyStatus::CuratorUnassigned;
954 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
955
956 Self::deposit_event(Event::<T, I>::CuratorUnassigned {
957 index: parent_bounty_id,
958 child_index: child_bounty_id,
959 });
960
961 Ok(())
962 }
963
964 #[pallet::call_index(5)]
989 #[pallet::weight(<T as Config<I>>::WeightInfo::award_bounty())]
990 pub fn award_bounty(
991 origin: OriginFor<T>,
992 #[pallet::compact] parent_bounty_id: BountyIndex,
993 child_bounty_id: Option<BountyIndex>,
994 beneficiary: BeneficiaryLookupOf<T, I>,
995 ) -> DispatchResult {
996 let signer = ensure_signed(origin)?;
997 let beneficiary = T::BeneficiaryLookup::lookup(beneficiary)?;
998
999 let (asset_kind, value, _, status, _) =
1000 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
1001
1002 if child_bounty_id.is_none() {
1003 ensure!(
1004 ChildBountiesPerParent::<T, I>::get(parent_bounty_id) == 0,
1005 Error::<T, I>::HasActiveChildBounty
1006 );
1007 }
1008
1009 let BountyStatus::Active { ref curator } = status else {
1010 return Err(Error::<T, I>::UnexpectedStatus.into())
1011 };
1012 ensure!(signer == *curator, Error::<T, I>::RequireCurator);
1013
1014 let beneficiary_payment_status = Self::do_process_payout_payment(
1015 parent_bounty_id,
1016 child_bounty_id,
1017 asset_kind,
1018 value,
1019 beneficiary.clone(),
1020 None,
1021 )?;
1022
1023 let new_status = BountyStatus::PayoutAttempted {
1024 curator: curator.clone(),
1025 beneficiary: beneficiary.clone(),
1026 payment_status: beneficiary_payment_status.clone(),
1027 };
1028 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
1029
1030 Self::deposit_event(Event::<T, I>::BountyAwarded {
1031 index: parent_bounty_id,
1032 child_index: child_bounty_id,
1033 beneficiary,
1034 });
1035
1036 Ok(())
1037 }
1038
1039 #[pallet::call_index(6)]
1063 #[pallet::weight(match child_bounty_id {
1064 None => <T as Config<I>>::WeightInfo::close_parent_bounty(),
1065 Some(_) => <T as Config<I>>::WeightInfo::close_child_bounty(),
1066 })]
1067 pub fn close_bounty(
1068 origin: OriginFor<T>,
1069 #[pallet::compact] parent_bounty_id: BountyIndex,
1070 child_bounty_id: Option<BountyIndex>,
1071 ) -> DispatchResult {
1072 let maybe_sender = ensure_signed(origin.clone())
1073 .map(Some)
1074 .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?;
1075
1076 let (asset_kind, value, _, status, parent_curator) =
1077 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
1078
1079 let maybe_curator = match status {
1080 BountyStatus::Funded { curator } | BountyStatus::Active { curator, .. } =>
1081 Some(curator),
1082 BountyStatus::CuratorUnassigned => None,
1083 _ => return Err(Error::<T, I>::UnexpectedStatus.into()),
1084 };
1085
1086 match child_bounty_id {
1087 None => {
1088 ensure!(
1090 ChildBountiesPerParent::<T, I>::get(parent_bounty_id) == 0,
1091 Error::<T, I>::HasActiveChildBounty
1092 );
1093 if let Some(sender) = maybe_sender.as_ref() {
1095 let is_curator =
1096 maybe_curator.as_ref().map_or(false, |curator| curator == sender);
1097 ensure!(is_curator, BadOrigin);
1098 }
1099 },
1100 Some(_) => {
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 let is_parent_curator = parent_curator
1106 .as_ref()
1107 .map_or(false, |parent_curator| parent_curator == sender);
1108 ensure!(is_curator || is_parent_curator, BadOrigin);
1109 }
1110 },
1111 };
1112
1113 let payment_status = Self::do_process_refund_payment(
1114 parent_bounty_id,
1115 child_bounty_id,
1116 asset_kind,
1117 value,
1118 None,
1119 )?;
1120 let new_status = BountyStatus::RefundAttempted {
1121 payment_status: payment_status.clone(),
1122 curator: maybe_curator.clone(),
1123 };
1124 Self::update_bounty_status(parent_bounty_id, child_bounty_id, new_status)?;
1125
1126 Self::deposit_event(Event::<T, I>::BountyCanceled {
1127 index: parent_bounty_id,
1128 child_index: child_bounty_id,
1129 });
1130
1131 Ok(())
1132 }
1133
1134 #[pallet::call_index(7)]
1160 #[pallet::weight(<T as Config<I>>::WeightInfo::check_status_funding().max(
1161 <T as Config<I>>::WeightInfo::check_status_refund(),
1162 ).max(<T as Config<I>>::WeightInfo::check_status_payout()))]
1163 pub fn check_status(
1164 origin: OriginFor<T>,
1165 #[pallet::compact] parent_bounty_id: BountyIndex,
1166 child_bounty_id: Option<BountyIndex>,
1167 ) -> DispatchResultWithPostInfo {
1168 use BountyStatus::*;
1169
1170 ensure_signed(origin)?;
1171 let (asset_kind, value, metadata, status, parent_curator) =
1172 Self::get_bounty_details(parent_bounty_id, child_bounty_id)?;
1173
1174 let (new_status, weight) = match status {
1175 FundingAttempted { ref payment_status, curator } => {
1176 let new_payment_status = Self::do_check_funding_payment_status(
1177 parent_bounty_id,
1178 child_bounty_id,
1179 payment_status.clone(),
1180 )?;
1181
1182 let new_status = match new_payment_status {
1183 PaymentState::Succeeded => match (child_bounty_id, parent_curator) {
1184 (Some(_), Some(parent_curator)) if curator == parent_curator =>
1185 BountyStatus::Active { curator },
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 PaymentStatus::Failure => {
1642 Self::deposit_event(Event::<T, I>::PaymentFailed {
1643 index: parent_bounty_id,
1644 child_index: child_bounty_id,
1645 payment_id,
1646 });
1647 return Ok(PaymentState::Failed)
1648 },
1649 }
1650 }
1651
1652 fn do_process_refund_payment(
1655 parent_bounty_id: BountyIndex,
1656 child_bounty_id: Option<BountyIndex>,
1657 asset_kind: T::AssetKind,
1658 value: T::Balance,
1659 payment_status: Option<PaymentState<PaymentIdOf<T, I>>>,
1660 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1661 if let Some(payment_status) = payment_status {
1662 ensure!(payment_status.is_pending_or_failed(), Error::<T, I>::UnexpectedStatus);
1663 }
1664
1665 let (source, beneficiary) = match child_bounty_id {
1666 None => (
1667 Self::bounty_account(parent_bounty_id, asset_kind.clone())?,
1668 Self::funding_source_account(asset_kind.clone())?,
1669 ),
1670 Some(child_bounty_id) => (
1671 Self::child_bounty_account(parent_bounty_id, child_bounty_id, asset_kind.clone())?,
1672 Self::bounty_account(parent_bounty_id, asset_kind.clone())?,
1673 ),
1674 };
1675
1676 let id = <T as Config<I>>::Paymaster::pay(&source, &beneficiary, asset_kind, value)
1677 .map_err(|_| Error::<T, I>::RefundError)?;
1678
1679 Self::deposit_event(Event::<T, I>::Paid {
1680 index: parent_bounty_id,
1681 child_index: child_bounty_id,
1682 payment_id: id,
1683 });
1684
1685 Ok(PaymentState::Attempted { id })
1686 }
1687
1688 fn do_check_refund_payment_status(
1691 parent_bounty_id: BountyIndex,
1692 child_bounty_id: Option<BountyIndex>,
1693 payment_status: PaymentState<PaymentIdOf<T, I>>,
1694 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1695 let payment_id = payment_status.get_attempt_id().ok_or(Error::<T, I>::UnexpectedStatus)?;
1696
1697 match <T as pallet::Config<I>>::Paymaster::check_payment(payment_id) {
1698 PaymentStatus::Success => {
1699 Self::deposit_event(Event::<T, I>::BountyRefundProcessed {
1700 index: parent_bounty_id,
1701 child_index: child_bounty_id,
1702 });
1703 Ok(PaymentState::Succeeded)
1704 },
1705 PaymentStatus::InProgress | PaymentStatus::Unknown =>
1706 Err(Error::<T, I>::RefundInconclusive.into()),
1708 PaymentStatus::Failure => {
1709 Self::deposit_event(Event::<T, I>::PaymentFailed {
1711 index: parent_bounty_id,
1712 child_index: child_bounty_id,
1713 payment_id,
1714 });
1715 Ok(PaymentState::Failed)
1716 },
1717 }
1718 }
1719
1720 fn do_process_payout_payment(
1722 parent_bounty_id: BountyIndex,
1723 child_bounty_id: Option<BountyIndex>,
1724 asset_kind: T::AssetKind,
1725 value: T::Balance,
1726 beneficiary: T::Beneficiary,
1727 payment_status: Option<PaymentState<PaymentIdOf<T, I>>>,
1728 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1729 if let Some(payment_status) = payment_status {
1730 ensure!(payment_status.is_pending_or_failed(), Error::<T, I>::UnexpectedStatus);
1731 }
1732
1733 let payout = Self::calculate_payout(parent_bounty_id, child_bounty_id, value);
1734
1735 let source = match child_bounty_id {
1736 None => Self::bounty_account(parent_bounty_id, asset_kind.clone())?,
1737 Some(child_bounty_id) =>
1738 Self::child_bounty_account(parent_bounty_id, child_bounty_id, asset_kind.clone())?,
1739 };
1740
1741 let id = <T as Config<I>>::Paymaster::pay(&source, &beneficiary, asset_kind, payout)
1742 .map_err(|_| Error::<T, I>::PayoutError)?;
1743
1744 Self::deposit_event(Event::<T, I>::Paid {
1745 index: parent_bounty_id,
1746 child_index: child_bounty_id,
1747 payment_id: id,
1748 });
1749
1750 Ok(PaymentState::Attempted { id })
1751 }
1752
1753 fn do_check_payout_payment_status(
1756 parent_bounty_id: BountyIndex,
1757 child_bounty_id: Option<BountyIndex>,
1758 asset_kind: T::AssetKind,
1759 value: T::Balance,
1760 beneficiary: T::Beneficiary,
1761 payment_status: PaymentState<PaymentIdOf<T, I>>,
1762 ) -> Result<PaymentState<PaymentIdOf<T, I>>, DispatchError> {
1763 let payment_id = payment_status.get_attempt_id().ok_or(Error::<T, I>::UnexpectedStatus)?;
1764
1765 match <T as pallet::Config<I>>::Paymaster::check_payment(payment_id) {
1766 PaymentStatus::Success => {
1767 let payout = Self::calculate_payout(parent_bounty_id, child_bounty_id, value);
1768
1769 Self::deposit_event(Event::<T, I>::BountyPayoutProcessed {
1770 index: parent_bounty_id,
1771 child_index: child_bounty_id,
1772 asset_kind: asset_kind.clone(),
1773 value: payout,
1774 beneficiary,
1775 });
1776
1777 Ok(PaymentState::Succeeded)
1778 },
1779 PaymentStatus::InProgress | PaymentStatus::Unknown =>
1780 Err(Error::<T, I>::PayoutInconclusive.into()),
1782 PaymentStatus::Failure => {
1783 Self::deposit_event(Event::<T, I>::PaymentFailed {
1785 index: parent_bounty_id,
1786 child_index: child_bounty_id,
1787 payment_id,
1788 });
1789 Ok(PaymentState::Failed)
1790 },
1791 }
1792 }
1793}
1794
1795pub struct CuratorDepositAmount<Mult, Min, Max, Balance>(PhantomData<(Mult, Min, Max, Balance)>);
1800impl<Mult, Min, Max, Balance> Convert<Balance, Balance>
1801 for CuratorDepositAmount<Mult, Min, Max, Balance>
1802where
1803 Balance: frame_support::traits::tokens::Balance,
1804 Min: Get<Option<Balance>>,
1805 Max: Get<Option<Balance>>,
1806 Mult: Get<Permill>,
1807{
1808 fn convert(value: Balance) -> Balance {
1809 let mut deposit = Mult::get().mul_floor(value);
1810
1811 if let Some(min) = Min::get() {
1812 if deposit < min {
1813 deposit = min;
1814 }
1815 }
1816
1817 if let Some(max) = Max::get() {
1818 if deposit > max {
1819 deposit = max;
1820 }
1821 }
1822
1823 deposit
1824 }
1825}
1826
1827pub struct PalletIdAsFundingSource<Id, T, C, I = ()>(PhantomData<(Id, T, C, I)>);
1838impl<Id, T, C, I> TryConvert<T::AssetKind, T::Beneficiary> for PalletIdAsFundingSource<Id, T, C, I>
1839where
1840 Id: Get<PalletId>,
1841 T: crate::Config<I>,
1842 C: Convert<T::AccountId, T::Beneficiary>,
1843{
1844 fn try_convert(_asset_kind: T::AssetKind) -> Result<T::Beneficiary, T::AssetKind> {
1845 let account: T::AccountId = Id::get().into_account_truncating();
1846 Ok(C::convert(account))
1847 }
1848}
1849
1850pub struct BountySourceFromPalletId<Id, T, C, I = ()>(PhantomData<(Id, T, C, I)>);
1861impl<Id, T, C, I> TryConvert<(BountyIndex, T::AssetKind), T::Beneficiary>
1862 for BountySourceFromPalletId<Id, T, C, I>
1863where
1864 Id: Get<PalletId>,
1865 T: crate::Config<I>,
1866 C: Convert<T::AccountId, T::Beneficiary>,
1867{
1868 fn try_convert(
1869 (parent_bounty_id, _asset_kind): (BountyIndex, T::AssetKind),
1870 ) -> Result<T::Beneficiary, (BountyIndex, T::AssetKind)> {
1871 let account: T::AccountId = Id::get().into_sub_account_truncating(("bt", parent_bounty_id));
1872 Ok(C::convert(account))
1873 }
1874}
1875
1876pub struct ChildBountySourceFromPalletId<Id, T, C, I = ()>(PhantomData<(Id, T, C, I)>);
1887impl<Id, T, C, I> TryConvert<(BountyIndex, BountyIndex, T::AssetKind), T::Beneficiary>
1888 for ChildBountySourceFromPalletId<Id, T, C, I>
1889where
1890 Id: Get<PalletId>,
1891 T: crate::Config<I>,
1892 C: Convert<T::AccountId, T::Beneficiary>,
1893{
1894 fn try_convert(
1895 (parent_bounty_id, child_bounty_id, _asset_kind): (BountyIndex, BountyIndex, T::AssetKind),
1896 ) -> Result<T::Beneficiary, (BountyIndex, BountyIndex, T::AssetKind)> {
1897 let account: T::AccountId =
1900 Id::get().into_sub_account_truncating(("cb", parent_bounty_id, child_bounty_id));
1901 Ok(C::convert(account))
1902 }
1903}