1#![cfg_attr(not(feature = "std"), no_std)]
54
55mod benchmarking;
56pub mod migration;
57mod tests;
58pub mod weights;
59
60extern crate alloc;
61
62const LOG_TARGET: &str = "runtime::child-bounties";
64
65use alloc::vec::Vec;
66
67use frame_support::traits::{
68 Currency,
69 ExistenceRequirement::{AllowDeath, KeepAlive},
70 Get, OnUnbalanced, ReservableCurrency, WithdrawReasons,
71};
72
73use sp_runtime::{
74 traits::{
75 AccountIdConversion, BadOrigin, BlockNumberProvider, CheckedSub, Saturating, StaticLookup,
76 Zero,
77 },
78 DispatchResult, RuntimeDebug,
79};
80
81use frame_support::pallet_prelude::*;
82use frame_system::pallet_prelude::{
83 ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor,
84};
85use pallet_bounties::BountyStatus;
86use scale_info::TypeInfo;
87pub use weights::WeightInfo;
88
89pub use pallet::*;
90
91pub type BalanceOf<T> = pallet_treasury::BalanceOf<T>;
92pub type BountiesError<T> = pallet_bounties::Error<T>;
93pub type BountyIndex = pallet_bounties::BountyIndex;
94pub type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
95pub type BlockNumberFor<T> =
96 <<T as pallet_treasury::Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
97
98#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
100pub struct ChildBounty<AccountId, Balance, BlockNumber> {
101 pub parent_bounty: BountyIndex,
103 pub value: Balance,
105 pub fee: Balance,
107 pub curator_deposit: Balance,
109 pub status: ChildBountyStatus<AccountId, BlockNumber>,
111}
112
113#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
115pub enum ChildBountyStatus<AccountId, BlockNumber> {
116 Added,
118 CuratorProposed {
121 curator: AccountId,
123 },
124 Active {
126 curator: AccountId,
128 },
129 PendingPayout {
131 curator: AccountId,
133 beneficiary: AccountId,
135 unlock_at: BlockNumber,
137 },
138}
139
140#[frame_support::pallet]
141pub mod pallet {
142
143 use super::*;
144
145 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
147
148 #[pallet::pallet]
149 #[pallet::storage_version(STORAGE_VERSION)]
150 pub struct Pallet<T>(_);
151
152 #[pallet::config]
153 pub trait Config:
154 frame_system::Config + pallet_treasury::Config + pallet_bounties::Config
155 {
156 #[pallet::constant]
158 type MaxActiveChildBountyCount: Get<u32>;
159
160 #[pallet::constant]
162 type ChildBountyValueMinimum: Get<BalanceOf<Self>>;
163
164 #[allow(deprecated)]
166 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
167
168 type WeightInfo: WeightInfo;
170 }
171
172 #[pallet::error]
173 pub enum Error<T> {
174 ParentBountyNotActive,
176 InsufficientBountyBalance,
178 TooManyChildBounties,
180 }
181
182 #[pallet::event]
183 #[pallet::generate_deposit(pub(super) fn deposit_event)]
184 pub enum Event<T: Config> {
185 Added { index: BountyIndex, child_index: BountyIndex },
187 Awarded { index: BountyIndex, child_index: BountyIndex, beneficiary: T::AccountId },
189 Claimed {
191 index: BountyIndex,
192 child_index: BountyIndex,
193 payout: BalanceOf<T>,
194 beneficiary: T::AccountId,
195 },
196 Canceled { index: BountyIndex, child_index: BountyIndex },
198 }
199
200 #[pallet::storage]
203 pub type ChildBountyCount<T: Config> = StorageValue<_, BountyIndex, ValueQuery>;
204
205 #[pallet::storage]
208 pub type ParentChildBounties<T: Config> =
209 StorageMap<_, Twox64Concat, BountyIndex, u32, ValueQuery>;
210
211 #[pallet::storage]
213 pub type ParentTotalChildBounties<T: Config> =
214 StorageMap<_, Twox64Concat, BountyIndex, u32, ValueQuery>;
215
216 #[pallet::storage]
218 pub type ChildBounties<T: Config> = StorageDoubleMap<
219 _,
220 Twox64Concat,
221 BountyIndex,
222 Twox64Concat,
223 BountyIndex,
224 ChildBounty<T::AccountId, BalanceOf<T>, BlockNumberFor<T>>,
225 >;
226
227 #[pallet::storage]
231 pub type ChildBountyDescriptionsV1<T: Config> = StorageDoubleMap<
232 _,
233 Twox64Concat,
234 BountyIndex,
235 Twox64Concat,
236 BountyIndex,
237 BoundedVec<u8, T::MaximumReasonLength>,
238 >;
239
240 #[pallet::storage]
246 pub type V0ToV1ChildBountyIds<T: Config> =
247 StorageMap<_, Twox64Concat, BountyIndex, (BountyIndex, BountyIndex)>;
248
249 #[pallet::storage]
251 pub type ChildrenCuratorFees<T: Config> =
252 StorageMap<_, Twox64Concat, BountyIndex, BalanceOf<T>, ValueQuery>;
253
254 #[pallet::call]
255 impl<T: Config> Pallet<T> {
256 #[pallet::call_index(0)]
276 #[pallet::weight(<T as Config>::WeightInfo::add_child_bounty(description.len() as u32))]
277 pub fn add_child_bounty(
278 origin: OriginFor<T>,
279 #[pallet::compact] parent_bounty_id: BountyIndex,
280 #[pallet::compact] value: BalanceOf<T>,
281 description: Vec<u8>,
282 ) -> DispatchResult {
283 let signer = ensure_signed(origin)?;
284
285 let bounded_description =
287 description.try_into().map_err(|_| BountiesError::<T>::ReasonTooBig)?;
288 ensure!(value >= T::ChildBountyValueMinimum::get(), BountiesError::<T>::InvalidValue);
289 ensure!(
290 ParentChildBounties::<T>::get(parent_bounty_id) <=
291 T::MaxActiveChildBountyCount::get() as u32,
292 Error::<T>::TooManyChildBounties,
293 );
294
295 let (curator, _) = Self::ensure_bounty_active(parent_bounty_id)?;
296 ensure!(signer == curator, BountiesError::<T>::RequireCurator);
297
298 let parent_bounty_account =
300 pallet_bounties::Pallet::<T>::bounty_account_id(parent_bounty_id);
301
302 let bounty_balance = T::Currency::free_balance(&parent_bounty_account);
304 let new_bounty_balance = bounty_balance
305 .checked_sub(&value)
306 .ok_or(Error::<T>::InsufficientBountyBalance)?;
307 T::Currency::ensure_can_withdraw(
308 &parent_bounty_account,
309 value,
310 WithdrawReasons::TRANSFER,
311 new_bounty_balance,
312 )?;
313
314 let child_bounty_id = ParentTotalChildBounties::<T>::get(parent_bounty_id);
316 let child_bounty_account =
317 Self::child_bounty_account_id(parent_bounty_id, child_bounty_id);
318
319 T::Currency::transfer(&parent_bounty_account, &child_bounty_account, value, KeepAlive)?;
321
322 ParentChildBounties::<T>::mutate(parent_bounty_id, |count| count.saturating_inc());
324 ParentTotalChildBounties::<T>::insert(
325 parent_bounty_id,
326 child_bounty_id.saturating_add(1),
327 );
328
329 Self::create_child_bounty(
331 parent_bounty_id,
332 child_bounty_id,
333 value,
334 bounded_description,
335 );
336 Ok(())
337 }
338
339 #[pallet::call_index(1)]
355 #[pallet::weight(<T as Config>::WeightInfo::propose_curator())]
356 pub fn propose_curator(
357 origin: OriginFor<T>,
358 #[pallet::compact] parent_bounty_id: BountyIndex,
359 #[pallet::compact] child_bounty_id: BountyIndex,
360 curator: AccountIdLookupOf<T>,
361 #[pallet::compact] fee: BalanceOf<T>,
362 ) -> DispatchResult {
363 let signer = ensure_signed(origin)?;
364 let child_bounty_curator = T::Lookup::lookup(curator)?;
365
366 let (curator, _) = Self::ensure_bounty_active(parent_bounty_id)?;
367 ensure!(signer == curator, BountiesError::<T>::RequireCurator);
368
369 ChildBounties::<T>::try_mutate_exists(
371 parent_bounty_id,
372 child_bounty_id,
373 |maybe_child_bounty| -> DispatchResult {
374 let child_bounty =
375 maybe_child_bounty.as_mut().ok_or(BountiesError::<T>::InvalidIndex)?;
376
377 ensure!(
379 child_bounty.status == ChildBountyStatus::Added,
380 BountiesError::<T>::UnexpectedStatus,
381 );
382
383 ensure!(fee < child_bounty.value, BountiesError::<T>::InvalidFee);
385
386 ChildrenCuratorFees::<T>::mutate(parent_bounty_id, |value| {
390 *value = value.saturating_add(fee)
391 });
392
393 child_bounty.fee = fee;
395
396 child_bounty.status =
398 ChildBountyStatus::CuratorProposed { curator: child_bounty_curator };
399
400 Ok(())
401 },
402 )
403 }
404
405 #[pallet::call_index(2)]
425 #[pallet::weight(<T as Config>::WeightInfo::accept_curator())]
426 pub fn accept_curator(
427 origin: OriginFor<T>,
428 #[pallet::compact] parent_bounty_id: BountyIndex,
429 #[pallet::compact] child_bounty_id: BountyIndex,
430 ) -> DispatchResult {
431 let signer = ensure_signed(origin)?;
432
433 let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?;
434 ChildBounties::<T>::try_mutate_exists(
436 parent_bounty_id,
437 child_bounty_id,
438 |maybe_child_bounty| -> DispatchResult {
439 let child_bounty =
440 maybe_child_bounty.as_mut().ok_or(BountiesError::<T>::InvalidIndex)?;
441
442 if let ChildBountyStatus::CuratorProposed { ref curator } = child_bounty.status
444 {
445 ensure!(signer == *curator, BountiesError::<T>::RequireCurator);
446
447 let deposit = Self::calculate_curator_deposit(
449 &parent_curator,
450 curator,
451 &child_bounty.fee,
452 );
453
454 T::Currency::reserve(curator, deposit)?;
455 child_bounty.curator_deposit = deposit;
456
457 child_bounty.status =
458 ChildBountyStatus::Active { curator: curator.clone() };
459 Ok(())
460 } else {
461 Err(BountiesError::<T>::UnexpectedStatus.into())
462 }
463 },
464 )
465 }
466
467 #[pallet::call_index(3)]
502 #[pallet::weight(<T as Config>::WeightInfo::unassign_curator())]
503 pub fn unassign_curator(
504 origin: OriginFor<T>,
505 #[pallet::compact] parent_bounty_id: BountyIndex,
506 #[pallet::compact] child_bounty_id: BountyIndex,
507 ) -> DispatchResult {
508 let maybe_sender = ensure_signed(origin.clone())
509 .map(Some)
510 .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?;
511
512 ChildBounties::<T>::try_mutate_exists(
513 parent_bounty_id,
514 child_bounty_id,
515 |maybe_child_bounty| -> DispatchResult {
516 let child_bounty =
517 maybe_child_bounty.as_mut().ok_or(BountiesError::<T>::InvalidIndex)?;
518
519 let slash_curator =
520 |curator: &T::AccountId, curator_deposit: &mut BalanceOf<T>| {
521 let imbalance =
522 T::Currency::slash_reserved(curator, *curator_deposit).0;
523 T::OnSlash::on_unbalanced(imbalance);
524 *curator_deposit = Zero::zero();
525 };
526
527 match child_bounty.status {
528 ChildBountyStatus::Added => {
529 return Err(BountiesError::<T>::UnexpectedStatus.into())
531 },
532 ChildBountyStatus::CuratorProposed { ref curator } => {
533 ensure!(
537 maybe_sender.map_or(true, |sender| {
538 sender == *curator ||
539 Self::ensure_bounty_active(parent_bounty_id)
540 .map_or(false, |(parent_curator, _)| {
541 sender == parent_curator
542 })
543 }),
544 BadOrigin
545 );
546 },
548 ChildBountyStatus::Active { ref curator } => {
549 match maybe_sender {
551 None => {
554 slash_curator(curator, &mut child_bounty.curator_deposit);
555 },
557 Some(sender) if sender == *curator => {
558 T::Currency::unreserve(curator, child_bounty.curator_deposit);
561 child_bounty.curator_deposit = Zero::zero();
563 },
565 Some(sender) => {
566 let (parent_curator, update_due) =
567 Self::ensure_bounty_active(parent_bounty_id)?;
568 if sender == parent_curator ||
569 update_due < Self::treasury_block_number()
570 {
571 slash_curator(curator, &mut child_bounty.curator_deposit);
575 } else {
577 return Err(BountiesError::<T>::Premature.into())
579 }
580 },
581 }
582 },
583 ChildBountyStatus::PendingPayout { ref curator, .. } => {
584 let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?;
585 ensure!(
586 maybe_sender.map_or(true, |sender| parent_curator == sender),
587 BadOrigin,
588 );
589 slash_curator(curator, &mut child_bounty.curator_deposit);
590 },
592 };
593 child_bounty.status = ChildBountyStatus::Added;
595 Ok(())
596 },
597 )
598 }
599
600 #[pallet::call_index(4)]
618 #[pallet::weight(<T as Config>::WeightInfo::award_child_bounty())]
619 pub fn award_child_bounty(
620 origin: OriginFor<T>,
621 #[pallet::compact] parent_bounty_id: BountyIndex,
622 #[pallet::compact] child_bounty_id: BountyIndex,
623 beneficiary: AccountIdLookupOf<T>,
624 ) -> DispatchResult {
625 let signer = ensure_signed(origin)?;
626 let beneficiary = T::Lookup::lookup(beneficiary)?;
627
628 let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?;
630
631 ChildBounties::<T>::try_mutate_exists(
632 parent_bounty_id,
633 child_bounty_id,
634 |maybe_child_bounty| -> DispatchResult {
635 let child_bounty =
636 maybe_child_bounty.as_mut().ok_or(BountiesError::<T>::InvalidIndex)?;
637
638 if let ChildBountyStatus::Active { ref curator } = child_bounty.status {
640 ensure!(
641 signer == *curator || signer == parent_curator,
642 BountiesError::<T>::RequireCurator,
643 );
644 child_bounty.status = ChildBountyStatus::PendingPayout {
646 curator: signer,
647 beneficiary: beneficiary.clone(),
648 unlock_at: Self::treasury_block_number() +
649 T::BountyDepositPayoutDelay::get(),
650 };
651 Ok(())
652 } else {
653 Err(BountiesError::<T>::UnexpectedStatus.into())
654 }
655 },
656 )?;
657
658 Self::deposit_event(Event::<T>::Awarded {
660 index: parent_bounty_id,
661 child_index: child_bounty_id,
662 beneficiary,
663 });
664
665 Ok(())
666 }
667
668 #[pallet::call_index(5)]
685 #[pallet::weight(<T as Config>::WeightInfo::claim_child_bounty())]
686 pub fn claim_child_bounty(
687 origin: OriginFor<T>,
688 #[pallet::compact] parent_bounty_id: BountyIndex,
689 #[pallet::compact] child_bounty_id: BountyIndex,
690 ) -> DispatchResult {
691 ensure_signed(origin)?;
692
693 ChildBounties::<T>::try_mutate_exists(
695 parent_bounty_id,
696 child_bounty_id,
697 |maybe_child_bounty| -> DispatchResult {
698 let child_bounty =
699 maybe_child_bounty.as_mut().ok_or(BountiesError::<T>::InvalidIndex)?;
700
701 if let ChildBountyStatus::PendingPayout {
702 ref curator,
703 ref beneficiary,
704 ref unlock_at,
705 } = child_bounty.status
706 {
707 ensure!(
710 Self::treasury_block_number() >= *unlock_at,
711 BountiesError::<T>::Premature,
712 );
713
714 let child_bounty_account =
716 Self::child_bounty_account_id(parent_bounty_id, child_bounty_id);
717 let balance = T::Currency::free_balance(&child_bounty_account);
718 let curator_fee = child_bounty.fee.min(balance);
719 let payout = balance.saturating_sub(curator_fee);
720
721 let _ = T::Currency::unreserve(curator, child_bounty.curator_deposit);
725
726 let fee_transfer_result = T::Currency::transfer(
729 &child_bounty_account,
730 curator,
731 curator_fee,
732 AllowDeath,
733 );
734 debug_assert!(fee_transfer_result.is_ok());
735
736 let payout_transfer_result = T::Currency::transfer(
739 &child_bounty_account,
740 beneficiary,
741 payout,
742 AllowDeath,
743 );
744 debug_assert!(payout_transfer_result.is_ok());
745
746 Self::deposit_event(Event::<T>::Claimed {
748 index: parent_bounty_id,
749 child_index: child_bounty_id,
750 payout,
751 beneficiary: beneficiary.clone(),
752 });
753
754 ParentChildBounties::<T>::mutate(parent_bounty_id, |count| {
756 count.saturating_dec()
757 });
758
759 ChildBountyDescriptionsV1::<T>::remove(parent_bounty_id, child_bounty_id);
761
762 *maybe_child_bounty = None;
764
765 Ok(())
766 } else {
767 Err(BountiesError::<T>::UnexpectedStatus.into())
768 }
769 },
770 )
771 }
772
773 #[pallet::call_index(6)]
796 #[pallet::weight(<T as Config>::WeightInfo::close_child_bounty_added()
797 .max(<T as Config>::WeightInfo::close_child_bounty_active()))]
798 pub fn close_child_bounty(
799 origin: OriginFor<T>,
800 #[pallet::compact] parent_bounty_id: BountyIndex,
801 #[pallet::compact] child_bounty_id: BountyIndex,
802 ) -> DispatchResult {
803 let maybe_sender = ensure_signed(origin.clone())
804 .map(Some)
805 .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?;
806
807 let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?;
809
810 ensure!(maybe_sender.map_or(true, |sender| parent_curator == sender), BadOrigin);
811
812 Self::impl_close_child_bounty(parent_bounty_id, child_bounty_id)?;
813 Ok(())
814 }
815 }
816
817 #[pallet::hooks]
818 impl<T: Config> Hooks<SystemBlockNumberFor<T>> for Pallet<T> {
819 fn integrity_test() {
820 let parent_bounty_id: BountyIndex = 1;
821 let child_bounty_id: BountyIndex = 2;
822 let _: T::AccountId = T::PalletId::get()
823 .try_into_sub_account(("cb", parent_bounty_id, child_bounty_id))
824 .expect(
825 "The `AccountId` type must be large enough to fit the child bounty account ID.",
826 );
827 }
828 }
829}
830
831impl<T: Config> Pallet<T> {
832 pub fn treasury_block_number() -> BlockNumberFor<T> {
836 <T as pallet_treasury::Config>::BlockNumberProvider::current_block_number()
837 }
838
839 fn calculate_curator_deposit(
841 parent_curator: &T::AccountId,
842 child_curator: &T::AccountId,
843 bounty_fee: &BalanceOf<T>,
844 ) -> BalanceOf<T> {
845 if parent_curator == child_curator {
846 return Zero::zero()
847 }
848
849 pallet_bounties::Pallet::<T>::calculate_curator_deposit(bounty_fee)
851 }
852
853 pub fn child_bounty_account_id(
855 parent_bounty_id: BountyIndex,
856 child_bounty_id: BountyIndex,
857 ) -> T::AccountId {
858 T::PalletId::get().into_sub_account_truncating(("cb", parent_bounty_id, child_bounty_id))
862 }
863
864 fn create_child_bounty(
865 parent_bounty_id: BountyIndex,
866 child_bounty_id: BountyIndex,
867 child_bounty_value: BalanceOf<T>,
868 description: BoundedVec<u8, T::MaximumReasonLength>,
869 ) {
870 let child_bounty = ChildBounty {
871 parent_bounty: parent_bounty_id,
872 value: child_bounty_value,
873 fee: 0u32.into(),
874 curator_deposit: 0u32.into(),
875 status: ChildBountyStatus::Added,
876 };
877 ChildBounties::<T>::insert(parent_bounty_id, child_bounty_id, &child_bounty);
878 ChildBountyDescriptionsV1::<T>::insert(parent_bounty_id, child_bounty_id, description);
879 Self::deposit_event(Event::Added { index: parent_bounty_id, child_index: child_bounty_id });
880 }
881
882 fn ensure_bounty_active(
883 bounty_id: BountyIndex,
884 ) -> Result<(T::AccountId, BlockNumberFor<T>), DispatchError> {
885 let parent_bounty = pallet_bounties::Bounties::<T>::get(bounty_id)
886 .ok_or(BountiesError::<T>::InvalidIndex)?;
887 if let BountyStatus::Active { curator, update_due } = parent_bounty.get_status() {
888 Ok((curator, update_due))
889 } else {
890 Err(Error::<T>::ParentBountyNotActive.into())
891 }
892 }
893
894 fn impl_close_child_bounty(
895 parent_bounty_id: BountyIndex,
896 child_bounty_id: BountyIndex,
897 ) -> DispatchResult {
898 ChildBounties::<T>::try_mutate_exists(
899 parent_bounty_id,
900 child_bounty_id,
901 |maybe_child_bounty| -> DispatchResult {
902 let child_bounty =
903 maybe_child_bounty.as_mut().ok_or(BountiesError::<T>::InvalidIndex)?;
904
905 match &child_bounty.status {
906 ChildBountyStatus::Added | ChildBountyStatus::CuratorProposed { .. } => {
907 },
909 ChildBountyStatus::Active { curator } => {
910 let _ = T::Currency::unreserve(curator, child_bounty.curator_deposit);
913 },
915 ChildBountyStatus::PendingPayout { .. } => {
916 return Err(BountiesError::<T>::PendingPayout.into())
922 },
923 }
924
925 ChildrenCuratorFees::<T>::mutate(parent_bounty_id, |value| {
928 *value = value.saturating_sub(child_bounty.fee)
929 });
930 ParentChildBounties::<T>::mutate(parent_bounty_id, |count| {
931 *count = count.saturating_sub(1)
932 });
933
934 let parent_bounty_account =
936 pallet_bounties::Pallet::<T>::bounty_account_id(parent_bounty_id);
937 let child_bounty_account =
938 Self::child_bounty_account_id(parent_bounty_id, child_bounty_id);
939 let balance = T::Currency::free_balance(&child_bounty_account);
940 let transfer_result = T::Currency::transfer(
941 &child_bounty_account,
942 &parent_bounty_account,
943 balance,
944 AllowDeath,
945 ); debug_assert!(transfer_result.is_ok());
947
948 ChildBountyDescriptionsV1::<T>::remove(parent_bounty_id, child_bounty_id);
950
951 *maybe_child_bounty = None;
952
953 Self::deposit_event(Event::<T>::Canceled {
954 index: parent_bounty_id,
955 child_index: child_bounty_id,
956 });
957 Ok(())
958 },
959 )
960 }
961}
962
963impl<T: Config> pallet_bounties::ChildBountyManager<BalanceOf<T>> for Pallet<T> {
970 fn child_bounties_count(
972 bounty_id: pallet_bounties::BountyIndex,
973 ) -> pallet_bounties::BountyIndex {
974 ParentChildBounties::<T>::get(bounty_id)
975 }
976
977 fn children_curator_fees(bounty_id: pallet_bounties::BountyIndex) -> BalanceOf<T> {
980 let children_fee_total = ChildrenCuratorFees::<T>::get(bounty_id);
983 ChildrenCuratorFees::<T>::remove(bounty_id);
984 children_fee_total
985 }
986
987 fn bounty_removed(bounty_id: BountyIndex) {
989 debug_assert!(ParentChildBounties::<T>::get(bounty_id).is_zero());
990 debug_assert!(ChildrenCuratorFees::<T>::get(bounty_id).is_zero());
991 debug_assert!(ChildBounties::<T>::iter_key_prefix(bounty_id).count().is_zero());
992 debug_assert!(ChildBountyDescriptionsV1::<T>::iter_key_prefix(bounty_id).count().is_zero());
993 ParentChildBounties::<T>::remove(bounty_id);
994 ParentTotalChildBounties::<T>::remove(bounty_id);
995 }
996}