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}