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 Debug, DispatchResult,
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, Debug, 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, Debug, 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 ParentChildBounties<T: Config> =
204 StorageMap<_, Twox64Concat, BountyIndex, u32, ValueQuery>;
205
206 #[pallet::storage]
208 pub type ParentTotalChildBounties<T: Config> =
209 StorageMap<_, Twox64Concat, BountyIndex, u32, ValueQuery>;
210
211 #[pallet::storage]
213 pub type ChildBounties<T: Config> = StorageDoubleMap<
214 _,
215 Twox64Concat,
216 BountyIndex,
217 Twox64Concat,
218 BountyIndex,
219 ChildBounty<T::AccountId, BalanceOf<T>, BlockNumberFor<T>>,
220 >;
221
222 #[pallet::storage]
226 pub type ChildBountyDescriptionsV1<T: Config> = StorageDoubleMap<
227 _,
228 Twox64Concat,
229 BountyIndex,
230 Twox64Concat,
231 BountyIndex,
232 BoundedVec<u8, T::MaximumReasonLength>,
233 >;
234
235 #[pallet::storage]
241 pub type V0ToV1ChildBountyIds<T: Config> =
242 StorageMap<_, Twox64Concat, BountyIndex, (BountyIndex, BountyIndex)>;
243
244 #[pallet::storage]
246 pub type ChildrenCuratorFees<T: Config> =
247 StorageMap<_, Twox64Concat, BountyIndex, BalanceOf<T>, ValueQuery>;
248
249 #[pallet::call]
250 impl<T: Config> Pallet<T> {
251 #[pallet::call_index(0)]
271 #[pallet::weight(<T as Config>::WeightInfo::add_child_bounty(description.len() as u32))]
272 pub fn add_child_bounty(
273 origin: OriginFor<T>,
274 #[pallet::compact] parent_bounty_id: BountyIndex,
275 #[pallet::compact] value: BalanceOf<T>,
276 description: Vec<u8>,
277 ) -> DispatchResult {
278 let signer = ensure_signed(origin)?;
279
280 let bounded_description =
282 description.try_into().map_err(|_| BountiesError::<T>::ReasonTooBig)?;
283 ensure!(value >= T::ChildBountyValueMinimum::get(), BountiesError::<T>::InvalidValue);
284 ensure!(
285 ParentChildBounties::<T>::get(parent_bounty_id) <
286 T::MaxActiveChildBountyCount::get(),
287 Error::<T>::TooManyChildBounties,
288 );
289
290 let (curator, _) = Self::ensure_bounty_active(parent_bounty_id)?;
291 ensure!(signer == curator, BountiesError::<T>::RequireCurator);
292
293 let parent_bounty_account =
295 pallet_bounties::Pallet::<T>::bounty_account_id(parent_bounty_id);
296
297 let bounty_balance = T::Currency::free_balance(&parent_bounty_account);
299 let new_bounty_balance = bounty_balance
300 .checked_sub(&value)
301 .ok_or(Error::<T>::InsufficientBountyBalance)?;
302 T::Currency::ensure_can_withdraw(
303 &parent_bounty_account,
304 value,
305 WithdrawReasons::TRANSFER,
306 new_bounty_balance,
307 )?;
308
309 let child_bounty_id = ParentTotalChildBounties::<T>::get(parent_bounty_id);
311 let child_bounty_account =
312 Self::child_bounty_account_id(parent_bounty_id, child_bounty_id);
313
314 T::Currency::transfer(&parent_bounty_account, &child_bounty_account, value, KeepAlive)?;
316
317 ParentChildBounties::<T>::mutate(parent_bounty_id, |count| count.saturating_inc());
319 ParentTotalChildBounties::<T>::insert(
320 parent_bounty_id,
321 child_bounty_id.saturating_add(1),
322 );
323
324 Self::create_child_bounty(
326 parent_bounty_id,
327 child_bounty_id,
328 value,
329 bounded_description,
330 );
331 Ok(())
332 }
333
334 #[pallet::call_index(1)]
350 #[pallet::weight(<T as Config>::WeightInfo::propose_curator())]
351 pub fn propose_curator(
352 origin: OriginFor<T>,
353 #[pallet::compact] parent_bounty_id: BountyIndex,
354 #[pallet::compact] child_bounty_id: BountyIndex,
355 curator: AccountIdLookupOf<T>,
356 #[pallet::compact] fee: BalanceOf<T>,
357 ) -> DispatchResult {
358 let signer = ensure_signed(origin)?;
359 let child_bounty_curator = T::Lookup::lookup(curator)?;
360
361 let (curator, _) = Self::ensure_bounty_active(parent_bounty_id)?;
362 ensure!(signer == curator, BountiesError::<T>::RequireCurator);
363
364 ChildBounties::<T>::try_mutate_exists(
366 parent_bounty_id,
367 child_bounty_id,
368 |maybe_child_bounty| -> DispatchResult {
369 let child_bounty =
370 maybe_child_bounty.as_mut().ok_or(BountiesError::<T>::InvalidIndex)?;
371
372 ensure!(
374 child_bounty.status == ChildBountyStatus::Added,
375 BountiesError::<T>::UnexpectedStatus,
376 );
377
378 ensure!(fee < child_bounty.value, BountiesError::<T>::InvalidFee);
380
381 ChildrenCuratorFees::<T>::mutate(parent_bounty_id, |value| {
385 *value = value.saturating_add(fee)
386 });
387
388 child_bounty.fee = fee;
390
391 child_bounty.status =
393 ChildBountyStatus::CuratorProposed { curator: child_bounty_curator };
394
395 Ok(())
396 },
397 )
398 }
399
400 #[pallet::call_index(2)]
420 #[pallet::weight(<T as Config>::WeightInfo::accept_curator())]
421 pub fn accept_curator(
422 origin: OriginFor<T>,
423 #[pallet::compact] parent_bounty_id: BountyIndex,
424 #[pallet::compact] child_bounty_id: BountyIndex,
425 ) -> DispatchResult {
426 let signer = ensure_signed(origin)?;
427
428 let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?;
429 ChildBounties::<T>::try_mutate_exists(
431 parent_bounty_id,
432 child_bounty_id,
433 |maybe_child_bounty| -> DispatchResult {
434 let child_bounty =
435 maybe_child_bounty.as_mut().ok_or(BountiesError::<T>::InvalidIndex)?;
436
437 if let ChildBountyStatus::CuratorProposed { ref curator } = child_bounty.status
439 {
440 ensure!(signer == *curator, BountiesError::<T>::RequireCurator);
441
442 let deposit = Self::calculate_curator_deposit(
444 &parent_curator,
445 curator,
446 &child_bounty.fee,
447 );
448
449 T::Currency::reserve(curator, deposit)?;
450 child_bounty.curator_deposit = deposit;
451
452 child_bounty.status =
453 ChildBountyStatus::Active { curator: curator.clone() };
454 Ok(())
455 } else {
456 Err(BountiesError::<T>::UnexpectedStatus.into())
457 }
458 },
459 )
460 }
461
462 #[pallet::call_index(3)]
497 #[pallet::weight(<T as Config>::WeightInfo::unassign_curator())]
498 pub fn unassign_curator(
499 origin: OriginFor<T>,
500 #[pallet::compact] parent_bounty_id: BountyIndex,
501 #[pallet::compact] child_bounty_id: BountyIndex,
502 ) -> DispatchResult {
503 let maybe_sender = ensure_signed(origin.clone())
504 .map(Some)
505 .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?;
506
507 ChildBounties::<T>::try_mutate_exists(
508 parent_bounty_id,
509 child_bounty_id,
510 |maybe_child_bounty| -> DispatchResult {
511 let child_bounty =
512 maybe_child_bounty.as_mut().ok_or(BountiesError::<T>::InvalidIndex)?;
513
514 let slash_curator =
515 |curator: &T::AccountId, curator_deposit: &mut BalanceOf<T>| {
516 let imbalance =
517 T::Currency::slash_reserved(curator, *curator_deposit).0;
518 T::OnSlash::on_unbalanced(imbalance);
519 *curator_deposit = Zero::zero();
520 };
521
522 match child_bounty.status {
523 ChildBountyStatus::Added => {
524 return Err(BountiesError::<T>::UnexpectedStatus.into());
526 },
527 ChildBountyStatus::CuratorProposed { ref curator } => {
528 ensure!(
532 maybe_sender.map_or(true, |sender| {
533 sender == *curator ||
534 Self::ensure_bounty_active(parent_bounty_id)
535 .map_or(false, |(parent_curator, _)| {
536 sender == parent_curator
537 })
538 }),
539 BadOrigin
540 );
541 },
543 ChildBountyStatus::Active { ref curator } => {
544 match maybe_sender {
546 None => {
549 slash_curator(curator, &mut child_bounty.curator_deposit);
550 },
552 Some(sender) if sender == *curator => {
553 T::Currency::unreserve(curator, child_bounty.curator_deposit);
556 child_bounty.curator_deposit = Zero::zero();
558 },
560 Some(sender) => {
561 let (parent_curator, update_due) =
562 Self::ensure_bounty_active(parent_bounty_id)?;
563 if sender == parent_curator ||
564 update_due < Self::treasury_block_number()
565 {
566 slash_curator(curator, &mut child_bounty.curator_deposit);
570 } else {
572 return Err(BountiesError::<T>::Premature.into());
574 }
575 },
576 }
577 },
578 ChildBountyStatus::PendingPayout { ref curator, .. } => {
579 let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?;
580 ensure!(
581 maybe_sender.map_or(true, |sender| parent_curator == sender),
582 BadOrigin,
583 );
584 slash_curator(curator, &mut child_bounty.curator_deposit);
585 },
587 };
588 child_bounty.status = ChildBountyStatus::Added;
590 Ok(())
591 },
592 )
593 }
594
595 #[pallet::call_index(4)]
613 #[pallet::weight(<T as Config>::WeightInfo::award_child_bounty())]
614 pub fn award_child_bounty(
615 origin: OriginFor<T>,
616 #[pallet::compact] parent_bounty_id: BountyIndex,
617 #[pallet::compact] child_bounty_id: BountyIndex,
618 beneficiary: AccountIdLookupOf<T>,
619 ) -> DispatchResult {
620 let signer = ensure_signed(origin)?;
621 let beneficiary = T::Lookup::lookup(beneficiary)?;
622
623 let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?;
625
626 ChildBounties::<T>::try_mutate_exists(
627 parent_bounty_id,
628 child_bounty_id,
629 |maybe_child_bounty| -> DispatchResult {
630 let child_bounty =
631 maybe_child_bounty.as_mut().ok_or(BountiesError::<T>::InvalidIndex)?;
632
633 if let ChildBountyStatus::Active { ref curator } = child_bounty.status {
635 ensure!(
636 signer == *curator || signer == parent_curator,
637 BountiesError::<T>::RequireCurator,
638 );
639 child_bounty.status = ChildBountyStatus::PendingPayout {
641 curator: signer,
642 beneficiary: beneficiary.clone(),
643 unlock_at: Self::treasury_block_number() +
644 T::BountyDepositPayoutDelay::get(),
645 };
646 Ok(())
647 } else {
648 Err(BountiesError::<T>::UnexpectedStatus.into())
649 }
650 },
651 )?;
652
653 Self::deposit_event(Event::<T>::Awarded {
655 index: parent_bounty_id,
656 child_index: child_bounty_id,
657 beneficiary,
658 });
659
660 Ok(())
661 }
662
663 #[pallet::call_index(5)]
680 #[pallet::weight(<T as Config>::WeightInfo::claim_child_bounty())]
681 pub fn claim_child_bounty(
682 origin: OriginFor<T>,
683 #[pallet::compact] parent_bounty_id: BountyIndex,
684 #[pallet::compact] child_bounty_id: BountyIndex,
685 ) -> DispatchResult {
686 ensure_signed(origin)?;
687
688 ChildBounties::<T>::try_mutate_exists(
690 parent_bounty_id,
691 child_bounty_id,
692 |maybe_child_bounty| -> DispatchResult {
693 let child_bounty =
694 maybe_child_bounty.as_mut().ok_or(BountiesError::<T>::InvalidIndex)?;
695
696 if let ChildBountyStatus::PendingPayout {
697 ref curator,
698 ref beneficiary,
699 ref unlock_at,
700 } = child_bounty.status
701 {
702 ensure!(
705 Self::treasury_block_number() >= *unlock_at,
706 BountiesError::<T>::Premature,
707 );
708
709 let child_bounty_account =
711 Self::child_bounty_account_id(parent_bounty_id, child_bounty_id);
712 let balance = T::Currency::free_balance(&child_bounty_account);
713 let curator_fee = child_bounty.fee.min(balance);
714 let payout = balance.saturating_sub(curator_fee);
715
716 let _ = T::Currency::unreserve(curator, child_bounty.curator_deposit);
720
721 let fee_transfer_result = T::Currency::transfer(
724 &child_bounty_account,
725 curator,
726 curator_fee,
727 AllowDeath,
728 );
729 debug_assert!(fee_transfer_result.is_ok());
730
731 let payout_transfer_result = T::Currency::transfer(
734 &child_bounty_account,
735 beneficiary,
736 payout,
737 AllowDeath,
738 );
739 debug_assert!(payout_transfer_result.is_ok());
740
741 Self::deposit_event(Event::<T>::Claimed {
743 index: parent_bounty_id,
744 child_index: child_bounty_id,
745 payout,
746 beneficiary: beneficiary.clone(),
747 });
748
749 ParentChildBounties::<T>::mutate(parent_bounty_id, |count| {
751 count.saturating_dec()
752 });
753
754 ChildBountyDescriptionsV1::<T>::remove(parent_bounty_id, child_bounty_id);
756
757 *maybe_child_bounty = None;
759
760 Ok(())
761 } else {
762 Err(BountiesError::<T>::UnexpectedStatus.into())
763 }
764 },
765 )
766 }
767
768 #[pallet::call_index(6)]
791 #[pallet::weight(<T as Config>::WeightInfo::close_child_bounty_added()
792 .max(<T as Config>::WeightInfo::close_child_bounty_active()))]
793 pub fn close_child_bounty(
794 origin: OriginFor<T>,
795 #[pallet::compact] parent_bounty_id: BountyIndex,
796 #[pallet::compact] child_bounty_id: BountyIndex,
797 ) -> DispatchResult {
798 let maybe_sender = ensure_signed(origin.clone())
799 .map(Some)
800 .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?;
801
802 let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?;
804
805 ensure!(maybe_sender.map_or(true, |sender| parent_curator == sender), BadOrigin);
806
807 Self::impl_close_child_bounty(parent_bounty_id, child_bounty_id)?;
808 Ok(())
809 }
810 }
811
812 #[pallet::hooks]
813 impl<T: Config> Hooks<SystemBlockNumberFor<T>> for Pallet<T> {
814 fn integrity_test() {
815 let parent_bounty_id: BountyIndex = 1;
816 let child_bounty_id: BountyIndex = 2;
817 let _: T::AccountId = T::PalletId::get()
818 .try_into_sub_account(("cb", parent_bounty_id, child_bounty_id))
819 .expect(
820 "The `AccountId` type must be large enough to fit the child bounty account ID.",
821 );
822 }
823 }
824}
825
826impl<T: Config> Pallet<T> {
827 pub fn treasury_block_number() -> BlockNumberFor<T> {
831 <T as pallet_treasury::Config>::BlockNumberProvider::current_block_number()
832 }
833
834 fn calculate_curator_deposit(
836 parent_curator: &T::AccountId,
837 child_curator: &T::AccountId,
838 bounty_fee: &BalanceOf<T>,
839 ) -> BalanceOf<T> {
840 if parent_curator == child_curator {
841 return Zero::zero();
842 }
843
844 pallet_bounties::Pallet::<T>::calculate_curator_deposit(bounty_fee)
846 }
847
848 pub fn child_bounty_account_id(
850 parent_bounty_id: BountyIndex,
851 child_bounty_id: BountyIndex,
852 ) -> T::AccountId {
853 T::PalletId::get().into_sub_account_truncating(("cb", parent_bounty_id, child_bounty_id))
857 }
858
859 fn create_child_bounty(
860 parent_bounty_id: BountyIndex,
861 child_bounty_id: BountyIndex,
862 child_bounty_value: BalanceOf<T>,
863 description: BoundedVec<u8, T::MaximumReasonLength>,
864 ) {
865 let child_bounty = ChildBounty {
866 parent_bounty: parent_bounty_id,
867 value: child_bounty_value,
868 fee: 0u32.into(),
869 curator_deposit: 0u32.into(),
870 status: ChildBountyStatus::Added,
871 };
872 ChildBounties::<T>::insert(parent_bounty_id, child_bounty_id, &child_bounty);
873 ChildBountyDescriptionsV1::<T>::insert(parent_bounty_id, child_bounty_id, description);
874 Self::deposit_event(Event::Added { index: parent_bounty_id, child_index: child_bounty_id });
875 }
876
877 fn ensure_bounty_active(
878 bounty_id: BountyIndex,
879 ) -> Result<(T::AccountId, BlockNumberFor<T>), DispatchError> {
880 let parent_bounty = pallet_bounties::Bounties::<T>::get(bounty_id)
881 .ok_or(BountiesError::<T>::InvalidIndex)?;
882 if let BountyStatus::Active { curator, update_due } = parent_bounty.get_status() {
883 Ok((curator, update_due))
884 } else {
885 Err(Error::<T>::ParentBountyNotActive.into())
886 }
887 }
888
889 fn impl_close_child_bounty(
890 parent_bounty_id: BountyIndex,
891 child_bounty_id: BountyIndex,
892 ) -> DispatchResult {
893 ChildBounties::<T>::try_mutate_exists(
894 parent_bounty_id,
895 child_bounty_id,
896 |maybe_child_bounty| -> DispatchResult {
897 let child_bounty =
898 maybe_child_bounty.as_mut().ok_or(BountiesError::<T>::InvalidIndex)?;
899
900 match &child_bounty.status {
901 ChildBountyStatus::Added | ChildBountyStatus::CuratorProposed { .. } => {
902 },
904 ChildBountyStatus::Active { curator } => {
905 let _ = T::Currency::unreserve(curator, child_bounty.curator_deposit);
908 },
910 ChildBountyStatus::PendingPayout { .. } => {
911 return Err(BountiesError::<T>::PendingPayout.into());
917 },
918 }
919
920 ChildrenCuratorFees::<T>::mutate(parent_bounty_id, |value| {
923 *value = value.saturating_sub(child_bounty.fee)
924 });
925 ParentChildBounties::<T>::mutate(parent_bounty_id, |count| {
926 *count = count.saturating_sub(1)
927 });
928
929 let parent_bounty_account =
931 pallet_bounties::Pallet::<T>::bounty_account_id(parent_bounty_id);
932 let child_bounty_account =
933 Self::child_bounty_account_id(parent_bounty_id, child_bounty_id);
934 let balance = T::Currency::free_balance(&child_bounty_account);
935 let transfer_result = T::Currency::transfer(
936 &child_bounty_account,
937 &parent_bounty_account,
938 balance,
939 AllowDeath,
940 ); debug_assert!(transfer_result.is_ok());
942
943 ChildBountyDescriptionsV1::<T>::remove(parent_bounty_id, child_bounty_id);
945
946 *maybe_child_bounty = None;
947
948 Self::deposit_event(Event::<T>::Canceled {
949 index: parent_bounty_id,
950 child_index: child_bounty_id,
951 });
952 Ok(())
953 },
954 )
955 }
956}
957
958impl<T: Config> pallet_bounties::ChildBountyManager<BalanceOf<T>> for Pallet<T> {
965 fn child_bounties_count(
967 bounty_id: pallet_bounties::BountyIndex,
968 ) -> pallet_bounties::BountyIndex {
969 ParentChildBounties::<T>::get(bounty_id)
970 }
971
972 fn children_curator_fees(bounty_id: pallet_bounties::BountyIndex) -> BalanceOf<T> {
975 let children_fee_total = ChildrenCuratorFees::<T>::get(bounty_id);
978 ChildrenCuratorFees::<T>::remove(bounty_id);
979 children_fee_total
980 }
981
982 fn bounty_removed(bounty_id: BountyIndex) {
984 debug_assert!(ParentChildBounties::<T>::get(bounty_id).is_zero());
985 debug_assert!(ChildrenCuratorFees::<T>::get(bounty_id).is_zero());
986 debug_assert!(ChildBounties::<T>::iter_key_prefix(bounty_id).count().is_zero());
987 debug_assert!(ChildBountyDescriptionsV1::<T>::iter_key_prefix(bounty_id).count().is_zero());
988 ParentChildBounties::<T>::remove(bounty_id);
989 ParentTotalChildBounties::<T>::remove(bounty_id);
990 }
991}