// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Child-bounties pallet tests.

#![cfg(test)]

use super::*;
use crate as pallet_child_bounties;

use frame_support::{
	assert_noop, assert_ok, derive_impl, parameter_types,
	traits::{
		tokens::{PayFromAccount, UnityAssetBalanceConversion},
		ConstU32, ConstU64, OnInitialize,
	},
	weights::Weight,
	PalletId,
};

use sp_runtime::{
	traits::{BadOrigin, IdentityLookup},
	BuildStorage, Perbill, Permill, TokenError,
};

use super::Event as ChildBountiesEvent;

type Block = frame_system::mocking::MockBlock<Test>;
type BountiesError = pallet_bounties::Error<Test>;

frame_support::construct_runtime!(
	pub enum Test
	{
		System: frame_system,
		Balances: pallet_balances,
		Bounties: pallet_bounties,
		Treasury: pallet_treasury,
		ChildBounties: pallet_child_bounties,
	}
);

parameter_types! {
	pub const MaximumBlockWeight: Weight = Weight::from_parts(1024, 0);
	pub const MaximumBlockLength: u32 = 2 * 1024;
	pub const AvailableBlockRatio: Perbill = Perbill::one();
}

type Balance = u64;

#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
	type AccountId = u128;
	type Lookup = IdentityLookup<Self::AccountId>;
	type Block = Block;
	type AccountData = pallet_balances::AccountData<u64>;
}

impl pallet_balances::Config for Test {
	type MaxLocks = ();
	type MaxReserves = ();
	type ReserveIdentifier = [u8; 8];
	type Balance = Balance;
	type RuntimeEvent = RuntimeEvent;
	type DustRemoval = ();
	type ExistentialDeposit = ConstU64<1>;
	type AccountStore = System;
	type WeightInfo = ();
	type FreezeIdentifier = ();
	type MaxFreezes = ();
	type RuntimeHoldReason = ();
	type RuntimeFreezeReason = ();
}
parameter_types! {
	pub const ProposalBond: Permill = Permill::from_percent(5);
	pub const Burn: Permill = Permill::from_percent(50);
	pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry");
	pub TreasuryAccount: u128 = Treasury::account_id();
	pub const SpendLimit: Balance = u64::MAX;
}

impl pallet_treasury::Config for Test {
	type PalletId = TreasuryPalletId;
	type Currency = pallet_balances::Pallet<Test>;
	type ApproveOrigin = frame_system::EnsureRoot<u128>;
	type RejectOrigin = frame_system::EnsureRoot<u128>;
	type RuntimeEvent = RuntimeEvent;
	type OnSlash = ();
	type ProposalBond = ProposalBond;
	type ProposalBondMinimum = ConstU64<1>;
	type ProposalBondMaximum = ();
	type SpendPeriod = ConstU64<2>;
	type Burn = Burn;
	type BurnDestination = ();
	type WeightInfo = ();
	type SpendFunds = Bounties;
	type MaxApprovals = ConstU32<100>;
	type SpendOrigin = frame_system::EnsureRootWithSuccess<Self::AccountId, SpendLimit>;
	type AssetKind = ();
	type Beneficiary = Self::AccountId;
	type BeneficiaryLookup = IdentityLookup<Self::Beneficiary>;
	type Paymaster = PayFromAccount<Balances, TreasuryAccount>;
	type BalanceConverter = UnityAssetBalanceConversion;
	type PayoutPeriod = ConstU64<10>;
	#[cfg(feature = "runtime-benchmarks")]
	type BenchmarkHelper = ();
}
parameter_types! {
	// This will be 50% of the bounty fee.
	pub const CuratorDepositMultiplier: Permill = Permill::from_percent(50);
	pub const CuratorDepositMax: Balance = 1_000;
	pub const CuratorDepositMin: Balance = 3;

}
impl pallet_bounties::Config for Test {
	type RuntimeEvent = RuntimeEvent;
	type BountyDepositBase = ConstU64<80>;
	type BountyDepositPayoutDelay = ConstU64<3>;
	type BountyUpdatePeriod = ConstU64<10>;
	type CuratorDepositMultiplier = CuratorDepositMultiplier;
	type CuratorDepositMax = CuratorDepositMax;
	type CuratorDepositMin = CuratorDepositMin;
	type BountyValueMinimum = ConstU64<5>;
	type DataDepositPerByte = ConstU64<1>;
	type MaximumReasonLength = ConstU32<300>;
	type WeightInfo = ();
	type ChildBountyManager = ChildBounties;
}
impl pallet_child_bounties::Config for Test {
	type RuntimeEvent = RuntimeEvent;
	type MaxActiveChildBountyCount = ConstU32<2>;
	type ChildBountyValueMinimum = ConstU64<1>;
	type WeightInfo = ();
}

pub fn new_test_ext() -> sp_io::TestExternalities {
	let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
	pallet_balances::GenesisConfig::<Test> {
		// Total issuance will be 200 with treasury account initialized at ED.
		balances: vec![(0, 100), (1, 98), (2, 1)],
	}
	.assimilate_storage(&mut t)
	.unwrap();
	pallet_treasury::GenesisConfig::<Test>::default()
		.assimilate_storage(&mut t)
		.unwrap();
	t.into()
}

fn last_event() -> ChildBountiesEvent<Test> {
	System::events()
		.into_iter()
		.map(|r| r.event)
		.filter_map(|e| if let RuntimeEvent::ChildBounties(inner) = e { Some(inner) } else { None })
		.last()
		.unwrap()
}

#[test]
fn genesis_config_works() {
	new_test_ext().execute_with(|| {
		assert_eq!(Treasury::pot(), 0);
		assert_eq!(Treasury::proposal_count(), 0);
	});
}

#[test]
fn minting_works() {
	new_test_ext().execute_with(|| {
		// Check that accumulate works when we have Some value in Dummy already.
		Balances::make_free_balance_be(&Treasury::account_id(), 101);
		assert_eq!(Treasury::pot(), 100);
	});
}

#[test]
fn add_child_bounty() {
	new_test_ext().execute_with(|| {
		// TestProcedure.
		// 1, Create bounty & move to active state with enough bounty fund & parent curator.
		// 2, Parent curator adds child-bounty child-bounty-1, test for error like RequireCurator
		//    ,InsufficientProposersBalance, InsufficientBountyBalance with invalid arguments.
		// 3, Parent curator adds child-bounty child-bounty-1, moves to "Approved" state &
		//    test for the event Added.
		// 4, Test for DB state of `Bounties` & `ChildBounties`.
		// 5, Observe fund transaction moment between Bounty, Child-bounty,
		//    Curator, child-bounty curator & beneficiary.

		// Make the parent bounty.
		System::set_block_number(1);
		Balances::make_free_balance_be(&Treasury::account_id(), 101);

		assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec()));

		assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));

		System::set_block_number(2);
		<Treasury as OnInitialize<u64>>::on_initialize(2);

		let fee = 8;
		assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, fee));

		Balances::make_free_balance_be(&4, 10);

		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0));

		// This verifies that the accept curator logic took a deposit.
		let expected_deposit = CuratorDepositMultiplier::get() * fee;
		assert_eq!(Balances::reserved_balance(&4), expected_deposit);
		assert_eq!(Balances::free_balance(&4), 10 - expected_deposit);

		// Add child-bounty.
		// Acc-4 is the parent curator.
		// Call from invalid origin & check for error "RequireCurator".
		assert_noop!(
			ChildBounties::add_child_bounty(RuntimeOrigin::signed(0), 0, 10, b"12345-p1".to_vec()),
			BountiesError::RequireCurator,
		);

		// Update the parent curator balance.
		Balances::make_free_balance_be(&4, 101);

		// parent curator fee is reserved on parent bounty account.
		assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 50);
		assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0);

		assert_noop!(
			ChildBounties::add_child_bounty(RuntimeOrigin::signed(4), 0, 50, b"12345-p1".to_vec()),
			TokenError::NotExpendable,
		);

		assert_noop!(
			ChildBounties::add_child_bounty(RuntimeOrigin::signed(4), 0, 100, b"12345-p1".to_vec()),
			Error::<Test>::InsufficientBountyBalance,
		);

		// Add child-bounty with valid value, which can be funded by parent bounty.
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(4),
			0,
			10,
			b"12345-p1".to_vec()
		));

		// Check for the event child-bounty added.
		assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 });

		assert_eq!(Balances::free_balance(4), 101);
		assert_eq!(Balances::reserved_balance(4), expected_deposit);

		// DB check.
		// Check the child-bounty status.
		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee: 0,
				curator_deposit: 0,
				status: ChildBountyStatus::Added,
			}
		);

		// Check the child-bounty count.
		assert_eq!(ChildBounties::parent_child_bounties(0), 1);

		// Check the child-bounty description status.
		assert_eq!(ChildBounties::child_bounty_descriptions(0).unwrap(), b"12345-p1".to_vec(),);
	});
}

#[test]
fn child_bounty_assign_curator() {
	new_test_ext().execute_with(|| {
		// TestProcedure
		// 1, Create bounty & move to active state with enough bounty fund & parent curator.
		// 2, Parent curator adds child-bounty child-bounty-1, moves to "Active" state.
		// 3, Test for DB state of `ChildBounties`.

		// Make the parent bounty.
		System::set_block_number(1);
		Balances::make_free_balance_be(&Treasury::account_id(), 101);
		Balances::make_free_balance_be(&4, 101);
		Balances::make_free_balance_be(&8, 101);

		assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec()));

		assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));

		System::set_block_number(2);
		<Treasury as OnInitialize<u64>>::on_initialize(2);

		let fee = 4;
		assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, fee));
		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0));

		// Bounty account status before adding child-bounty.
		assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 50);
		assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0);

		// Check the balance of parent curator.
		// Curator deposit is reserved for parent curator on parent bounty.
		let expected_deposit = Bounties::calculate_curator_deposit(&fee);
		assert_eq!(Balances::free_balance(4), 101 - expected_deposit);
		assert_eq!(Balances::reserved_balance(4), expected_deposit);

		// Add child-bounty.
		// Acc-4 is the parent curator & make sure enough deposit.
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(4),
			0,
			10,
			b"12345-p1".to_vec()
		));

		assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 });

		// Bounty account status after adding child-bounty.
		assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 40);
		assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0);

		// Child-bounty account status.
		assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 10);
		assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0);

		let fee = 6u64;
		assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, fee));

		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee,
				curator_deposit: 0,
				status: ChildBountyStatus::CuratorProposed { curator: 8 },
			}
		);

		// Check the balance of parent curator.
		assert_eq!(Balances::free_balance(4), 101 - expected_deposit);
		assert_eq!(Balances::reserved_balance(4), expected_deposit);

		assert_noop!(
			ChildBounties::accept_curator(RuntimeOrigin::signed(3), 0, 0),
			BountiesError::RequireCurator,
		);

		assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0));

		let expected_child_deposit = CuratorDepositMultiplier::get() * fee;

		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee,
				curator_deposit: expected_child_deposit,
				status: ChildBountyStatus::Active { curator: 8 },
			}
		);

		// Deposit for child-bounty curator deposit is reserved.
		assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit);
		assert_eq!(Balances::reserved_balance(8), expected_child_deposit);

		// Bounty account status at exit.
		assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 40);
		assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0);

		// Child-bounty account status at exit.
		assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 10);
		assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0);

		// Treasury account status at exit.
		assert_eq!(Balances::free_balance(Treasury::account_id()), 26);
		assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0);
	});
}

#[test]
fn award_claim_child_bounty() {
	new_test_ext().execute_with(|| {
		// Make the parent bounty.
		System::set_block_number(1);
		Balances::make_free_balance_be(&Treasury::account_id(), 101);
		assert_eq!(Balances::free_balance(Treasury::account_id()), 101);
		assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0);

		// Bounty curator initial balance.
		Balances::make_free_balance_be(&4, 101); // Parent-bounty curator.
		Balances::make_free_balance_be(&8, 101); // Child-bounty curator.

		assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec()));

		assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));

		System::set_block_number(2);
		<Treasury as OnInitialize<u64>>::on_initialize(2);

		assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6));
		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0));

		// Child-bounty.
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(4),
			0,
			10,
			b"12345-p1".to_vec()
		));

		assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 });

		// Propose and accept curator for child-bounty.
		let fee = 8;
		assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, fee));
		assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0));

		// Award child-bounty.
		// Test for non child-bounty curator.
		assert_noop!(
			ChildBounties::award_child_bounty(RuntimeOrigin::signed(3), 0, 0, 7),
			BountiesError::RequireCurator,
		);

		assert_ok!(ChildBounties::award_child_bounty(RuntimeOrigin::signed(8), 0, 0, 7));

		let expected_deposit = CuratorDepositMultiplier::get() * fee;
		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee,
				curator_deposit: expected_deposit,
				status: ChildBountyStatus::PendingPayout {
					curator: 8,
					beneficiary: 7,
					unlock_at: 5
				},
			}
		);

		// Claim child-bounty.
		// Test for Premature condition.
		assert_noop!(
			ChildBounties::claim_child_bounty(RuntimeOrigin::signed(7), 0, 0),
			BountiesError::Premature
		);

		System::set_block_number(9);

		assert_ok!(ChildBounties::claim_child_bounty(RuntimeOrigin::signed(7), 0, 0));

		// Ensure child-bounty curator is paid with curator fee & deposit refund.
		assert_eq!(Balances::free_balance(8), 101 + fee);
		assert_eq!(Balances::reserved_balance(8), 0);

		// Ensure executor is paid with beneficiary amount.
		assert_eq!(Balances::free_balance(7), 10 - fee);
		assert_eq!(Balances::reserved_balance(7), 0);

		// Child-bounty account status.
		assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 0);
		assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0);

		// Check the child-bounty count.
		assert_eq!(ChildBounties::parent_child_bounties(0), 0);
	});
}

#[test]
fn close_child_bounty_added() {
	new_test_ext().execute_with(|| {
		// Make the parent bounty.
		System::set_block_number(1);
		Balances::make_free_balance_be(&Treasury::account_id(), 101);
		assert_eq!(Balances::free_balance(Treasury::account_id()), 101);
		assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0);

		// Bounty curator initial balance.
		Balances::make_free_balance_be(&4, 101); // Parent-bounty curator.
		Balances::make_free_balance_be(&8, 101); // Child-bounty curator.

		assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec()));

		assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));

		System::set_block_number(2);
		<Treasury as OnInitialize<u64>>::on_initialize(2);

		assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6));

		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0));

		// Child-bounty.
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(4),
			0,
			10,
			b"12345-p1".to_vec()
		));

		assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 });

		System::set_block_number(4);

		// Close child-bounty.
		// Wrong origin.
		assert_noop!(ChildBounties::close_child_bounty(RuntimeOrigin::signed(7), 0, 0), BadOrigin);
		assert_noop!(ChildBounties::close_child_bounty(RuntimeOrigin::signed(8), 0, 0), BadOrigin);

		// Correct origin - parent curator.
		assert_ok!(ChildBounties::close_child_bounty(RuntimeOrigin::signed(4), 0, 0));

		// Check the child-bounty count.
		assert_eq!(ChildBounties::parent_child_bounties(0), 0);

		// Parent-bounty account status.
		assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 50);
		assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0);

		// Child-bounty account status.
		assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 0);
		assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0);
	});
}

#[test]
fn close_child_bounty_active() {
	new_test_ext().execute_with(|| {
		// Make the parent bounty.
		System::set_block_number(1);
		Balances::make_free_balance_be(&Treasury::account_id(), 101);
		assert_eq!(Balances::free_balance(Treasury::account_id()), 101);
		assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0);

		// Bounty curator initial balance.
		Balances::make_free_balance_be(&4, 101); // Parent-bounty curator.
		Balances::make_free_balance_be(&8, 101); // Child-bounty curator.

		assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec()));

		assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));

		System::set_block_number(2);
		<Treasury as OnInitialize<u64>>::on_initialize(2);

		assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6));

		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0));

		// Child-bounty.
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(4),
			0,
			10,
			b"12345-p1".to_vec()
		));

		assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 });

		// Propose and accept curator for child-bounty.
		assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, 2));
		assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0));

		// Close child-bounty in active state.
		assert_ok!(ChildBounties::close_child_bounty(RuntimeOrigin::signed(4), 0, 0));

		// Check the child-bounty count.
		assert_eq!(ChildBounties::parent_child_bounties(0), 0);

		// Ensure child-bounty curator balance is unreserved.
		assert_eq!(Balances::free_balance(8), 101);
		assert_eq!(Balances::reserved_balance(8), 0);

		// Parent-bounty account status.
		assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 50);
		assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0);

		// Child-bounty account status.
		assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 0);
		assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0);
	});
}

#[test]
fn close_child_bounty_pending() {
	new_test_ext().execute_with(|| {
		// Make the parent bounty.
		System::set_block_number(1);
		Balances::make_free_balance_be(&Treasury::account_id(), 101);
		assert_eq!(Balances::free_balance(Treasury::account_id()), 101);
		assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0);

		// Bounty curator initial balance.
		Balances::make_free_balance_be(&4, 101); // Parent-bounty curator.
		Balances::make_free_balance_be(&8, 101); // Child-bounty curator.

		assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec()));

		assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));

		System::set_block_number(2);
		<Treasury as OnInitialize<u64>>::on_initialize(2);

		let parent_fee = 6;
		assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, parent_fee));
		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0));

		// Child-bounty.
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(4),
			0,
			10,
			b"12345-p1".to_vec()
		));

		assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 });

		// Propose and accept curator for child-bounty.
		let child_fee = 4;
		assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, child_fee));
		assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0));
		let expected_child_deposit = CuratorDepositMin::get();

		assert_ok!(ChildBounties::award_child_bounty(RuntimeOrigin::signed(8), 0, 0, 7));

		// Close child-bounty in pending_payout state.
		assert_noop!(
			ChildBounties::close_child_bounty(RuntimeOrigin::signed(4), 0, 0),
			BountiesError::PendingPayout
		);

		// Check the child-bounty count.
		assert_eq!(ChildBounties::parent_child_bounties(0), 1);

		// Ensure no changes in child-bounty curator balance.
		assert_eq!(Balances::reserved_balance(8), expected_child_deposit);
		assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit);

		// Child-bounty account status.
		assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 10);
		assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0);
	});
}

#[test]
fn child_bounty_added_unassign_curator() {
	new_test_ext().execute_with(|| {
		// Make the parent bounty.
		System::set_block_number(1);
		Balances::make_free_balance_be(&Treasury::account_id(), 101);
		assert_eq!(Balances::free_balance(Treasury::account_id()), 101);
		assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0);

		// Bounty curator initial balance.
		Balances::make_free_balance_be(&4, 101); // Parent-bounty curator.
		Balances::make_free_balance_be(&8, 101); // Child-bounty curator.

		assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec()));

		assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));

		System::set_block_number(2);
		<Treasury as OnInitialize<u64>>::on_initialize(2);

		assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6));

		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0));

		// Child-bounty.
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(4),
			0,
			10,
			b"12345-p1".to_vec()
		));

		assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 });

		// Unassign curator in added state.
		assert_noop!(
			ChildBounties::unassign_curator(RuntimeOrigin::signed(4), 0, 0),
			BountiesError::UnexpectedStatus
		);
	});
}

#[test]
fn child_bounty_curator_proposed_unassign_curator() {
	new_test_ext().execute_with(|| {
		// Make the parent bounty.
		System::set_block_number(1);
		Balances::make_free_balance_be(&Treasury::account_id(), 101);
		assert_eq!(Balances::free_balance(Treasury::account_id()), 101);
		assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0);

		// Bounty curator initial balance.
		Balances::make_free_balance_be(&4, 101); // Parent-bounty curator.
		Balances::make_free_balance_be(&8, 101); // Child-bounty curator.

		assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec()));

		assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));

		System::set_block_number(2);
		<Treasury as OnInitialize<u64>>::on_initialize(2);

		assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6));

		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0));

		// Child-bounty.
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(4),
			0,
			10,
			b"12345-p1".to_vec()
		));

		assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 });

		// Propose curator for child-bounty.
		assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, 2));

		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee: 2,
				curator_deposit: 0,
				status: ChildBountyStatus::CuratorProposed { curator: 8 },
			}
		);

		// Random account cannot unassign the curator when in proposed state.
		assert_noop!(ChildBounties::unassign_curator(RuntimeOrigin::signed(99), 0, 0), BadOrigin);

		// Unassign curator.
		assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(4), 0, 0));

		// Verify updated child-bounty status.
		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee: 2,
				curator_deposit: 0,
				status: ChildBountyStatus::Added,
			}
		);
	});
}

#[test]
fn child_bounty_active_unassign_curator() {
	// Covers all scenarios with all origin types.
	// Step 1: Setup bounty, child bounty.
	// Step 2: Assign, accept curator for child bounty. Unassign from reject origin. Should slash.
	// Step 3: Assign, accept another curator for child bounty. Unassign from parent-bounty curator.
	// Should slash. Step 4: Assign, accept another curator for child bounty. Unassign from
	// child-bounty curator. Should NOT slash. Step 5: Assign, accept another curator for child
	// bounty. Unassign from random account. Should slash.
	new_test_ext().execute_with(|| {
		// Make the parent bounty.
		System::set_block_number(1);
		Balances::make_free_balance_be(&Treasury::account_id(), 101);
		assert_eq!(Balances::free_balance(Treasury::account_id()), 101);
		assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0);

		// Bounty curator initial balance.
		Balances::make_free_balance_be(&4, 101); // Parent-bounty curator.
		Balances::make_free_balance_be(&6, 101); // Child-bounty curator 1.
		Balances::make_free_balance_be(&7, 101); // Child-bounty curator 2.
		Balances::make_free_balance_be(&8, 101); // Child-bounty curator 3.

		assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec()));

		assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));

		System::set_block_number(2);
		<Treasury as OnInitialize<u64>>::on_initialize(2);

		assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6));
		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0));

		// Create Child-bounty.
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(4),
			0,
			10,
			b"12345-p1".to_vec()
		));
		assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 });

		System::set_block_number(3);
		<Treasury as OnInitialize<u64>>::on_initialize(3);

		// Propose and accept curator for child-bounty.
		let fee = 6;
		assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, fee));
		assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0));
		let expected_child_deposit = CuratorDepositMultiplier::get() * fee;

		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee,
				curator_deposit: expected_child_deposit,
				status: ChildBountyStatus::Active { curator: 8 },
			}
		);

		System::set_block_number(4);
		<Treasury as OnInitialize<u64>>::on_initialize(4);

		// Unassign curator - from reject origin.
		assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::root(), 0, 0));

		// Verify updated child-bounty status.
		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee,
				curator_deposit: 0,
				status: ChildBountyStatus::Added,
			}
		);

		// Ensure child-bounty curator was slashed.
		assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit);
		assert_eq!(Balances::reserved_balance(8), 0); // slashed

		// Propose and accept curator for child-bounty again.
		let fee = 2;
		assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 7, fee));
		assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(7), 0, 0));
		let expected_child_deposit = CuratorDepositMin::get();

		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee,
				curator_deposit: expected_child_deposit,
				status: ChildBountyStatus::Active { curator: 7 },
			}
		);

		System::set_block_number(5);
		<Treasury as OnInitialize<u64>>::on_initialize(5);

		// Unassign curator again - from parent curator.
		assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(4), 0, 0));

		// Verify updated child-bounty status.
		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee: 2,
				curator_deposit: 0,
				status: ChildBountyStatus::Added,
			}
		);

		// Ensure child-bounty curator was slashed.
		assert_eq!(Balances::free_balance(7), 101 - expected_child_deposit);
		assert_eq!(Balances::reserved_balance(7), 0); // slashed

		// Propose and accept curator for child-bounty again.
		assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 6, 2));
		assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(6), 0, 0));

		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee,
				curator_deposit: expected_child_deposit,
				status: ChildBountyStatus::Active { curator: 6 },
			}
		);

		System::set_block_number(6);
		<Treasury as OnInitialize<u64>>::on_initialize(6);

		// Unassign curator again - from child-bounty curator.
		assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(6), 0, 0));

		// Verify updated child-bounty status.
		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee: 2,
				curator_deposit: 0,
				status: ChildBountyStatus::Added,
			}
		);

		// Ensure child-bounty curator was **not** slashed.
		assert_eq!(Balances::free_balance(6), 101); // not slashed
		assert_eq!(Balances::reserved_balance(6), 0);

		// Propose and accept curator for child-bounty one last time.
		let fee = 2;
		assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 6, fee));
		assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(6), 0, 0));
		let expected_child_deposit = CuratorDepositMin::get();

		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee,
				curator_deposit: expected_child_deposit,
				status: ChildBountyStatus::Active { curator: 6 },
			}
		);

		System::set_block_number(7);
		<Treasury as OnInitialize<u64>>::on_initialize(7);

		// Unassign curator again - from non curator; non reject origin; some random guy.
		// Bounty update period is not yet complete.
		assert_noop!(
			ChildBounties::unassign_curator(RuntimeOrigin::signed(3), 0, 0),
			BountiesError::Premature
		);

		System::set_block_number(20);
		<Treasury as OnInitialize<u64>>::on_initialize(20);

		// Unassign child curator from random account after inactivity.
		assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(3), 0, 0));

		// Verify updated child-bounty status.
		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee: 2,
				curator_deposit: 0,
				status: ChildBountyStatus::Added,
			}
		);

		// Ensure child-bounty curator was slashed.
		assert_eq!(Balances::free_balance(6), 101 - expected_child_deposit); // slashed
		assert_eq!(Balances::reserved_balance(6), 0);
	});
}

#[test]
fn parent_bounty_inactive_unassign_curator_child_bounty() {
	// Unassign curator when parent bounty in not in active state.
	// This can happen when the curator of parent bounty has been unassigned.
	new_test_ext().execute_with(|| {
		// Make the parent bounty.
		System::set_block_number(1);
		Balances::make_free_balance_be(&Treasury::account_id(), 101);
		assert_eq!(Balances::free_balance(Treasury::account_id()), 101);
		assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0);

		// Bounty curator initial balance.
		Balances::make_free_balance_be(&4, 101); // Parent-bounty curator 1.
		Balances::make_free_balance_be(&5, 101); // Parent-bounty curator 2.
		Balances::make_free_balance_be(&6, 101); // Child-bounty curator 1.
		Balances::make_free_balance_be(&7, 101); // Child-bounty curator 2.
		Balances::make_free_balance_be(&8, 101); // Child-bounty curator 3.

		assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec()));
		assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));

		System::set_block_number(2);
		<Treasury as OnInitialize<u64>>::on_initialize(2);

		assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6));
		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0));

		// Create Child-bounty.
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(4),
			0,
			10,
			b"12345-p1".to_vec()
		));
		assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 });

		System::set_block_number(3);
		<Treasury as OnInitialize<u64>>::on_initialize(3);

		// Propose and accept curator for child-bounty.
		let fee = 8;
		assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, fee));
		assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0));
		let expected_child_deposit = CuratorDepositMultiplier::get() * fee;

		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee,
				curator_deposit: expected_child_deposit,
				status: ChildBountyStatus::Active { curator: 8 },
			}
		);

		System::set_block_number(4);
		<Treasury as OnInitialize<u64>>::on_initialize(4);

		// Unassign parent bounty curator.
		assert_ok!(Bounties::unassign_curator(RuntimeOrigin::root(), 0));

		System::set_block_number(5);
		<Treasury as OnInitialize<u64>>::on_initialize(5);

		// Try unassign child-bounty curator - from non curator; non reject
		// origin; some random guy. Bounty update period is not yet complete.
		assert_noop!(
			ChildBounties::unassign_curator(RuntimeOrigin::signed(3), 0, 0),
			Error::<Test>::ParentBountyNotActive
		);

		// Unassign curator - from reject origin.
		assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::root(), 0, 0));

		// Verify updated child-bounty status.
		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee,
				curator_deposit: 0,
				status: ChildBountyStatus::Added,
			}
		);

		// Ensure child-bounty curator was slashed.
		assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit);
		assert_eq!(Balances::reserved_balance(8), 0); // slashed

		System::set_block_number(6);
		<Treasury as OnInitialize<u64>>::on_initialize(6);

		// Propose and accept curator for parent-bounty again.
		assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 5, 6));
		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(5), 0));

		System::set_block_number(7);
		<Treasury as OnInitialize<u64>>::on_initialize(7);

		// Propose and accept curator for child-bounty again.
		let fee = 2;
		assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(5), 0, 0, 7, fee));
		assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(7), 0, 0));
		let expected_deposit = CuratorDepositMin::get();

		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee,
				curator_deposit: expected_deposit,
				status: ChildBountyStatus::Active { curator: 7 },
			}
		);

		System::set_block_number(8);
		<Treasury as OnInitialize<u64>>::on_initialize(8);

		assert_noop!(
			ChildBounties::unassign_curator(RuntimeOrigin::signed(3), 0, 0),
			BountiesError::Premature
		);

		// Unassign parent bounty curator again.
		assert_ok!(Bounties::unassign_curator(RuntimeOrigin::signed(5), 0));

		System::set_block_number(9);
		<Treasury as OnInitialize<u64>>::on_initialize(9);

		// Unassign curator again - from parent curator.
		assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(7), 0, 0));

		// Verify updated child-bounty status.
		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee: 2,
				curator_deposit: 0,
				status: ChildBountyStatus::Added,
			}
		);

		// Ensure child-bounty curator was not slashed.
		assert_eq!(Balances::free_balance(7), 101);
		assert_eq!(Balances::reserved_balance(7), 0); // slashed
	});
}

#[test]
fn close_parent_with_child_bounty() {
	new_test_ext().execute_with(|| {
		// Make the parent bounty.
		System::set_block_number(1);
		Balances::make_free_balance_be(&Treasury::account_id(), 101);
		assert_eq!(Balances::free_balance(Treasury::account_id()), 101);
		assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0);

		// Bounty curator initial balance.
		Balances::make_free_balance_be(&4, 101); // Parent-bounty curator.
		Balances::make_free_balance_be(&8, 101); // Child-bounty curator.

		assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec()));
		assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));

		// Try add child-bounty.
		// Should fail, parent bounty not active yet.
		assert_noop!(
			ChildBounties::add_child_bounty(RuntimeOrigin::signed(4), 0, 10, b"12345-p1".to_vec()),
			Error::<Test>::ParentBountyNotActive
		);

		System::set_block_number(2);
		<Treasury as OnInitialize<u64>>::on_initialize(2);

		assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6));
		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0));

		// Child-bounty.
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(4),
			0,
			10,
			b"12345-p1".to_vec()
		));
		assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 });

		System::set_block_number(4);
		<Treasury as OnInitialize<u64>>::on_initialize(4);

		// Try close parent-bounty.
		// Child bounty active, can't close parent.
		assert_noop!(
			Bounties::close_bounty(RuntimeOrigin::root(), 0),
			BountiesError::HasActiveChildBounty
		);

		System::set_block_number(2);

		// Close child-bounty.
		assert_ok!(ChildBounties::close_child_bounty(RuntimeOrigin::root(), 0, 0));

		// Check the child-bounty count.
		assert_eq!(ChildBounties::parent_child_bounties(0), 0);

		// Try close parent-bounty again.
		// Should pass this time.
		assert_ok!(Bounties::close_bounty(RuntimeOrigin::root(), 0));
	});
}

#[test]
fn children_curator_fee_calculation_test() {
	// Tests the calculation of subtracting child-bounty curator fee
	// from parent bounty fee when claiming bounties.
	new_test_ext().execute_with(|| {
		// Make the parent bounty.
		System::set_block_number(1);
		Balances::make_free_balance_be(&Treasury::account_id(), 101);
		assert_eq!(Balances::free_balance(Treasury::account_id()), 101);
		assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0);

		// Bounty curator initial balance.
		Balances::make_free_balance_be(&4, 101); // Parent-bounty curator.
		Balances::make_free_balance_be(&8, 101); // Child-bounty curator.

		assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec()));
		assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));

		System::set_block_number(2);
		<Treasury as OnInitialize<u64>>::on_initialize(2);

		assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6));
		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0));

		// Child-bounty.
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(4),
			0,
			10,
			b"12345-p1".to_vec()
		));
		assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 });

		System::set_block_number(4);
		<Treasury as OnInitialize<u64>>::on_initialize(4);

		let fee = 6;

		// Propose curator for child-bounty.
		assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, fee));
		// Check curator fee added to the sum.
		assert_eq!(ChildBounties::children_curator_fees(0), fee);
		// Accept curator for child-bounty.
		assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0));
		// Award child-bounty.
		assert_ok!(ChildBounties::award_child_bounty(RuntimeOrigin::signed(8), 0, 0, 7));

		let expected_child_deposit = CuratorDepositMultiplier::get() * fee;

		assert_eq!(
			ChildBounties::child_bounties(0, 0).unwrap(),
			ChildBounty {
				parent_bounty: 0,
				value: 10,
				fee,
				curator_deposit: expected_child_deposit,
				status: ChildBountyStatus::PendingPayout {
					curator: 8,
					beneficiary: 7,
					unlock_at: 7
				},
			}
		);

		System::set_block_number(9);

		// Claim child-bounty.
		assert_ok!(ChildBounties::claim_child_bounty(RuntimeOrigin::signed(7), 0, 0));

		// Check the child-bounty count.
		assert_eq!(ChildBounties::parent_child_bounties(0), 0);

		// Award the parent bounty.
		assert_ok!(Bounties::award_bounty(RuntimeOrigin::signed(4), 0, 9));

		System::set_block_number(15);

		// Claim the parent bounty.
		assert_ok!(Bounties::claim_bounty(RuntimeOrigin::signed(9), 0));

		// Ensure parent-bounty curator received correctly reduced fee.
		assert_eq!(Balances::free_balance(4), 101 + 6 - fee); // 101 + 6 - 2
		assert_eq!(Balances::reserved_balance(4), 0);

		// Verify parent-bounty beneficiary balance.
		assert_eq!(Balances::free_balance(9), 34);
		assert_eq!(Balances::reserved_balance(9), 0);
	});
}

#[test]
fn accept_curator_handles_different_deposit_calculations() {
	// This test will verify that a bounty with and without a fee results
	// in a different curator deposit, and if the child curator matches the parent curator.
	new_test_ext().execute_with(|| {
		// Setup a parent bounty.
		let parent_curator = 0;
		let parent_index = 0;
		let parent_value = 1_000_000;
		let parent_fee = 10_000;

		System::set_block_number(1);
		Balances::make_free_balance_be(&Treasury::account_id(), parent_value * 3);
		Balances::make_free_balance_be(&parent_curator, parent_fee * 100);
		assert_ok!(Bounties::propose_bounty(
			RuntimeOrigin::signed(parent_curator),
			parent_value,
			b"12345".to_vec()
		));
		assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), parent_index));

		System::set_block_number(2);
		<Treasury as OnInitialize<u64>>::on_initialize(2);

		assert_ok!(Bounties::propose_curator(
			RuntimeOrigin::root(),
			parent_index,
			parent_curator,
			parent_fee
		));
		assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(parent_curator), parent_index));

		// Now we can start creating some child bounties.
		// Case 1: Parent and child curator are not the same.

		let child_index = 0;
		let child_curator = 1;
		let child_value = 1_000;
		let child_fee = 100;
		let starting_balance = 100 * child_fee + child_value;

		Balances::make_free_balance_be(&child_curator, starting_balance);
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(parent_curator),
			parent_index,
			child_value,
			b"12345-p1".to_vec()
		));
		System::set_block_number(3);
		<Treasury as OnInitialize<u64>>::on_initialize(3);
		assert_ok!(ChildBounties::propose_curator(
			RuntimeOrigin::signed(parent_curator),
			parent_index,
			child_index,
			child_curator,
			child_fee
		));
		assert_ok!(ChildBounties::accept_curator(
			RuntimeOrigin::signed(child_curator),
			parent_index,
			child_index
		));

		let expected_deposit = CuratorDepositMultiplier::get() * child_fee;
		assert_eq!(Balances::free_balance(child_curator), starting_balance - expected_deposit);
		assert_eq!(Balances::reserved_balance(child_curator), expected_deposit);

		// Case 2: Parent and child curator are the same.

		let child_index = 1;
		let child_curator = parent_curator; // The same as parent bounty curator
		let child_value = 1_000;
		let child_fee = 10;

		let free_before = Balances::free_balance(&parent_curator);
		let reserved_before = Balances::reserved_balance(&parent_curator);

		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(parent_curator),
			parent_index,
			child_value,
			b"12345-p1".to_vec()
		));
		System::set_block_number(4);
		<Treasury as OnInitialize<u64>>::on_initialize(4);
		assert_ok!(ChildBounties::propose_curator(
			RuntimeOrigin::signed(parent_curator),
			parent_index,
			child_index,
			child_curator,
			child_fee
		));
		assert_ok!(ChildBounties::accept_curator(
			RuntimeOrigin::signed(child_curator),
			parent_index,
			child_index
		));

		// No expected deposit
		assert_eq!(Balances::free_balance(child_curator), free_before);
		assert_eq!(Balances::reserved_balance(child_curator), reserved_before);

		// Case 3: Upper Limit

		let child_index = 2;
		let child_curator = 2;
		let child_value = 10_000;
		let child_fee = 5_000;

		Balances::make_free_balance_be(&child_curator, starting_balance);
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(parent_curator),
			parent_index,
			child_value,
			b"12345-p1".to_vec()
		));
		System::set_block_number(5);
		<Treasury as OnInitialize<u64>>::on_initialize(5);
		assert_ok!(ChildBounties::propose_curator(
			RuntimeOrigin::signed(parent_curator),
			parent_index,
			child_index,
			child_curator,
			child_fee
		));
		assert_ok!(ChildBounties::accept_curator(
			RuntimeOrigin::signed(child_curator),
			parent_index,
			child_index
		));

		let expected_deposit = CuratorDepositMax::get();
		assert_eq!(Balances::free_balance(child_curator), starting_balance - expected_deposit);
		assert_eq!(Balances::reserved_balance(child_curator), expected_deposit);

		// There is a max number of child bounties at a time.
		assert_ok!(ChildBounties::impl_close_child_bounty(parent_index, child_index));

		// Case 4: Lower Limit

		let child_index = 3;
		let child_curator = 3;
		let child_value = 10_000;
		let child_fee = 0;

		Balances::make_free_balance_be(&child_curator, starting_balance);
		assert_ok!(ChildBounties::add_child_bounty(
			RuntimeOrigin::signed(parent_curator),
			parent_index,
			child_value,
			b"12345-p1".to_vec()
		));
		System::set_block_number(5);
		<Treasury as OnInitialize<u64>>::on_initialize(5);
		assert_ok!(ChildBounties::propose_curator(
			RuntimeOrigin::signed(parent_curator),
			parent_index,
			child_index,
			child_curator,
			child_fee
		));
		assert_ok!(ChildBounties::accept_curator(
			RuntimeOrigin::signed(child_curator),
			parent_index,
			child_index
		));

		let expected_deposit = CuratorDepositMin::get();
		assert_eq!(Balances::free_balance(child_curator), starting_balance - expected_deposit);
		assert_eq!(Balances::reserved_balance(child_curator), expected_deposit);
	});
}
