diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index a6d97cb299b24..47bdedbab83a3 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -220,7 +220,7 @@ impl frame_system::Config for Runtime { type SystemWeightInfo = frame_system::weights::SubstrateWeight; type SS58Prefix = ConstU16<42>; type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; + type MaxConsumers = ConstU32<16>; } impl pallet_randomness_collective_flip::Config for Runtime {} @@ -827,7 +827,7 @@ impl pallet_democracy::Config for Runtime { type Slash = Treasury; type Scheduler = Scheduler; type PalletsOrigin = OriginCaller; - type MaxVotes = frame_support::traits::ConstU32<100>; + type MaxVotes = ConstU32<100>; type WeightInfo = pallet_democracy::weights::SubstrateWeight; type MaxProposals = MaxProposals; } @@ -929,17 +929,9 @@ parameter_types! { pub const TipFindersFee: Percent = Percent::from_percent(20); pub const TipReportDepositBase: Balance = 1 * DOLLARS; pub const DataDepositPerByte: Balance = 1 * CENTS; - pub const BountyDepositBase: Balance = 1 * DOLLARS; - pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS; pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); - pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS; pub const MaximumReasonLength: u32 = 300; - pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); - pub const BountyValueMinimum: Balance = 5 * DOLLARS; pub const MaxApprovals: u32 = 100; - pub const MaxActiveChildBountyCount: u32 = 5; - pub const ChildBountyValueMinimum: Balance = 1 * DOLLARS; - pub const ChildBountyCuratorDepositBase: Permill = Permill::from_percent(10); } impl pallet_treasury::Config for Runtime { @@ -966,12 +958,25 @@ impl pallet_treasury::Config for Runtime { type MaxApprovals = MaxApprovals; } +parameter_types! { + pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); + pub const BountyValueMinimum: Balance = 5 * DOLLARS; + pub const BountyDepositBase: Balance = 1 * DOLLARS; + pub const CuratorDepositMultiplier: Permill = Permill::from_percent(50); + pub const CuratorDepositMin: Balance = 1 * DOLLARS; + pub const CuratorDepositMax: Balance = 100 * DOLLARS; + pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS; + pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS; +} + impl pallet_bounties::Config for Runtime { type Event = Event; type BountyDepositBase = BountyDepositBase; type BountyDepositPayoutDelay = BountyDepositPayoutDelay; type BountyUpdatePeriod = BountyUpdatePeriod; - type BountyCuratorDeposit = BountyCuratorDeposit; + type CuratorDepositMultiplier = CuratorDepositMultiplier; + type CuratorDepositMin = CuratorDepositMin; + type CuratorDepositMax = CuratorDepositMax; type BountyValueMinimum = BountyValueMinimum; type DataDepositPerByte = DataDepositPerByte; type MaximumReasonLength = MaximumReasonLength; @@ -979,11 +984,14 @@ impl pallet_bounties::Config for Runtime { type ChildBountyManager = ChildBounties; } +parameter_types! { + pub const ChildBountyValueMinimum: Balance = 1 * DOLLARS; +} + impl pallet_child_bounties::Config for Runtime { type Event = Event; - type MaxActiveChildBountyCount = MaxActiveChildBountyCount; + type MaxActiveChildBountyCount = ConstU32<5>; type ChildBountyValueMinimum = ChildBountyValueMinimum; - type ChildBountyCuratorDepositBase = ChildBountyCuratorDepositBase; type WeightInfo = pallet_child_bounties::weights::SubstrateWeight; } diff --git a/frame/bounties/src/lib.rs b/frame/bounties/src/lib.rs index 988b15c58a13f..98f2da305a06d 100644 --- a/frame/bounties/src/lib.rs +++ b/frame/bounties/src/lib.rs @@ -196,10 +196,20 @@ pub mod pallet { #[pallet::constant] type BountyUpdatePeriod: Get; - /// Percentage of the curator fee that will be reserved upfront as deposit for bounty - /// curator. + /// The curator deposit is calculated as a percentage of the curator fee. + /// + /// This deposit has optional upper and lower bounds with `CuratorDepositMax` and + /// `CuratorDepositMin`. + #[pallet::constant] + type CuratorDepositMultiplier: Get; + + /// Maximum amount of funds that should be placed in a deposit for making a proposal. #[pallet::constant] - type BountyCuratorDeposit: Get; + type CuratorDepositMax: Get>>; + + /// Minimum amount of funds that should be placed in a deposit for making a proposal. + #[pallet::constant] + type CuratorDepositMin: Get>>; /// Minimum value for a bounty. #[pallet::constant] @@ -502,7 +512,7 @@ pub mod pallet { BountyStatus::CuratorProposed { ref curator } => { ensure!(signer == *curator, Error::::RequireCurator); - let deposit = T::BountyCuratorDeposit::get() * bounty.fee; + let deposit = Self::calculate_curator_deposit(&bounty.fee); T::Currency::reserve(curator, deposit)?; bounty.curator_deposit = deposit; @@ -762,7 +772,19 @@ pub mod pallet { } impl Pallet { - // Add public immutables and private mutables. + pub fn calculate_curator_deposit(fee: &BalanceOf) -> BalanceOf { + let mut deposit = T::CuratorDepositMultiplier::get() * *fee; + + if let Some(max_deposit) = T::CuratorDepositMax::get() { + deposit = deposit.min(max_deposit) + } + + if let Some(min_deposit) = T::CuratorDepositMin::get() { + deposit = deposit.max(min_deposit) + } + + deposit + } /// The account ID of the treasury pot. /// diff --git a/frame/bounties/src/tests.rs b/frame/bounties/src/tests.rs index 3206fce9912fa..9a84bd687abc1 100644 --- a/frame/bounties/src/tests.rs +++ b/frame/bounties/src/tests.rs @@ -60,6 +60,8 @@ parameter_types! { pub const AvailableBlockRatio: Perbill = Perbill::one(); } +type Balance = u64; + impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -91,7 +93,7 @@ impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; - type Balance = u64; + type Balance = Balance; type Event = Event; type DustRemoval = (); type ExistentialDeposit = ConstU64<1>; @@ -125,15 +127,23 @@ impl pallet_treasury::Config for Test { type SpendFunds = Bounties; type MaxApprovals = ConstU32<100>; } + parameter_types! { - pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); + // 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 Config for Test { type Event = Event; type BountyDepositBase = ConstU64<80>; type BountyDepositPayoutDelay = ConstU64<3>; type BountyUpdatePeriod = ConstU64<20>; - type BountyCuratorDeposit = BountyCuratorDeposit; + type CuratorDepositMultiplier = CuratorDepositMultiplier; + type CuratorDepositMax = CuratorDepositMax; + type CuratorDepositMin = CuratorDepositMin; type BountyValueMinimum = ConstU64<1>; type DataDepositPerByte = ConstU64<1>; type MaximumReasonLength = ConstU32<16384>; @@ -543,13 +553,14 @@ fn assign_curator_works() { Error::::InvalidFee ); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); + let fee = 4; + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, fee)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, + fee, curator_deposit: 0, value: 50, bond: 85, @@ -567,20 +578,22 @@ fn assign_curator_works() { assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); + let expected_deposit = Bounties::calculate_curator_deposit(&fee); + assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, - curator_deposit: 2, + fee, + curator_deposit: expected_deposit, value: 50, bond: 85, status: BountyStatus::Active { curator: 4, update_due: 22 }, } ); - assert_eq!(Balances::free_balance(&4), 8); - assert_eq!(Balances::reserved_balance(&4), 2); + assert_eq!(Balances::free_balance(&4), 10 - expected_deposit); + assert_eq!(Balances::reserved_balance(&4), expected_deposit); }); } @@ -596,17 +609,17 @@ fn unassign_curator_works() { System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); + let fee = 4; + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, fee)); assert_noop!(Bounties::unassign_curator(Origin::signed(1), 0), BadOrigin); - assert_ok!(Bounties::unassign_curator(Origin::signed(4), 0)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, + fee, curator_deposit: 0, value: 50, bond: 85, @@ -614,19 +627,17 @@ fn unassign_curator_works() { } ); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); - + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, fee)); Balances::make_free_balance_be(&4, 10); - assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); - + let expected_deposit = Bounties::calculate_curator_deposit(&fee); assert_ok!(Bounties::unassign_curator(Origin::root(), 0)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, + fee, curator_deposit: 0, value: 50, bond: 85, @@ -634,8 +645,8 @@ fn unassign_curator_works() { } ); - assert_eq!(Balances::free_balance(&4), 8); - assert_eq!(Balances::reserved_balance(&4), 0); // slashed 2 + assert_eq!(Balances::free_balance(&4), 10 - expected_deposit); + assert_eq!(Balances::reserved_balance(&4), 0); // slashed curator deposit }); } @@ -652,10 +663,12 @@ fn award_and_claim_bounty_works() { System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); + let fee = 4; + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, fee)); assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); - assert_eq!(Balances::free_balance(4), 8); // inital 10 - 2 deposit + let expected_deposit = Bounties::calculate_curator_deposit(&fee); + assert_eq!(Balances::free_balance(4), 10 - expected_deposit); assert_noop!( Bounties::award_bounty(Origin::signed(1), 0, 3), @@ -668,8 +681,8 @@ fn award_and_claim_bounty_works() { Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, - curator_deposit: 2, + fee, + curator_deposit: expected_deposit, value: 50, bond: 85, status: BountyStatus::PendingPayout { curator: 4, beneficiary: 3, unlock_at: 5 }, @@ -1034,3 +1047,78 @@ fn unassign_curator_self() { assert_eq!(Balances::reserved_balance(1), 0); // not slashed }); } + +#[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: one using the value, and one using the fee. + new_test_ext().execute_with(|| { + // Case 1: With a fee + let user = 1; + let bounty_index = 0; + let value = 88; + let fee = 42; + + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + Balances::make_free_balance_be(&user, 100); + assert_ok!(Bounties::propose_bounty(Origin::signed(0), value, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(Origin::root(), bounty_index)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator(Origin::root(), bounty_index, user, fee)); + assert_ok!(Bounties::accept_curator(Origin::signed(user), bounty_index)); + + let expected_deposit = CuratorDepositMultiplier::get() * fee; + assert_eq!(Balances::free_balance(&user), 100 - expected_deposit); + assert_eq!(Balances::reserved_balance(&user), expected_deposit); + + // Case 2: Lower bound + let user = 2; + let bounty_index = 1; + let value = 35; + let fee = 0; + + Balances::make_free_balance_be(&Treasury::account_id(), 101); + Balances::make_free_balance_be(&user, 100); + + assert_ok!(Bounties::propose_bounty(Origin::signed(0), value, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(Origin::root(), bounty_index)); + + System::set_block_number(3); + >::on_initialize(3); + + assert_ok!(Bounties::propose_curator(Origin::root(), bounty_index, user, fee)); + assert_ok!(Bounties::accept_curator(Origin::signed(user), bounty_index)); + + let expected_deposit = CuratorDepositMin::get(); + assert_eq!(Balances::free_balance(&user), 100 - expected_deposit); + assert_eq!(Balances::reserved_balance(&user), expected_deposit); + + // Case 3: Upper bound + let user = 3; + let bounty_index = 2; + let value = 1_000_000; + let fee = 50_000; + let starting_balance = fee * 2; + + Balances::make_free_balance_be(&Treasury::account_id(), value * 2); + Balances::make_free_balance_be(&user, starting_balance); + Balances::make_free_balance_be(&0, starting_balance); + + assert_ok!(Bounties::propose_bounty(Origin::signed(0), value, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(Origin::root(), bounty_index)); + + System::set_block_number(3); + >::on_initialize(3); + + assert_ok!(Bounties::propose_curator(Origin::root(), bounty_index, user, fee)); + assert_ok!(Bounties::accept_curator(Origin::signed(user), bounty_index)); + + let expected_deposit = CuratorDepositMax::get(); + assert_eq!(Balances::free_balance(&user), starting_balance - expected_deposit); + assert_eq!(Balances::reserved_balance(&user), expected_deposit); + }); +} diff --git a/frame/child-bounties/src/lib.rs b/frame/child-bounties/src/lib.rs index 36acc7766bf61..2fea61c045cf6 100644 --- a/frame/child-bounties/src/lib.rs +++ b/frame/child-bounties/src/lib.rs @@ -66,7 +66,7 @@ use frame_support::traits::{ use sp_runtime::{ traits::{AccountIdConversion, BadOrigin, CheckedSub, Saturating, StaticLookup, Zero}, - DispatchResult, Permill, RuntimeDebug, + DispatchResult, RuntimeDebug, }; use frame_support::pallet_prelude::*; @@ -144,11 +144,6 @@ pub mod pallet { #[pallet::constant] type ChildBountyValueMinimum: Get>; - /// Percentage of child-bounty value to be reserved as curator deposit - /// when curator fee is zero. - #[pallet::constant] - type ChildBountyCuratorDepositBase: Get; - /// The overarching event type. type Event: From> + IsType<::Event>; @@ -392,7 +387,7 @@ pub mod pallet { ) -> DispatchResult { let signer = ensure_signed(origin)?; - let _ = Self::ensure_bounty_active(parent_bounty_id)?; + let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?; // Mutate child-bounty. ChildBounties::::try_mutate_exists( parent_bounty_id, @@ -406,11 +401,13 @@ pub mod pallet { { ensure!(signer == *curator, BountiesError::::RequireCurator); - // Reserve child-bounty curator deposit. Curator deposit - // is reserved based on a percentage of child-bounty - // value instead of fee, to avoid no deposit in case the - // fee is set as zero. - let deposit = T::ChildBountyCuratorDepositBase::get() * child_bounty.value; + // Reserve child-bounty curator deposit. + let deposit = Self::calculate_curator_deposit( + &parent_curator, + curator, + &child_bounty.fee, + ); + T::Currency::reserve(curator, deposit)?; child_bounty.curator_deposit = deposit; @@ -770,6 +767,20 @@ pub mod pallet { } impl Pallet { + // This function will calculate the deposit of a curator. + fn calculate_curator_deposit( + parent_curator: &T::AccountId, + child_curator: &T::AccountId, + bounty_fee: &BalanceOf, + ) -> BalanceOf { + if parent_curator == child_curator { + return Zero::zero() + } + + // We just use the same logic from the parent bounties pallet. + pallet_bounties::Pallet::::calculate_curator_deposit(bounty_fee) + } + /// The account ID of a child-bounty account. pub fn child_bounty_account_id(id: BountyIndex) -> T::AccountId { // This function is taken from the parent (bounties) pallet, but the diff --git a/frame/child-bounties/src/tests.rs b/frame/child-bounties/src/tests.rs index a6748c47b73d8..61545561a26c3 100644 --- a/frame/child-bounties/src/tests.rs +++ b/frame/child-bounties/src/tests.rs @@ -65,6 +65,8 @@ parameter_types! { pub const AvailableBlockRatio: Perbill = Perbill::one(); } +type Balance = u64; + impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -96,7 +98,7 @@ impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; - type Balance = u64; + type Balance = Balance; type Event = Event; type DustRemoval = (); type ExistentialDeposit = ConstU64<1>; @@ -130,28 +132,30 @@ impl pallet_treasury::Config for Test { type MaxApprovals = ConstU32<100>; } parameter_types! { - pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); + // 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 Event = Event; type BountyDepositBase = ConstU64<80>; type BountyDepositPayoutDelay = ConstU64<3>; type BountyUpdatePeriod = ConstU64<10>; - type BountyCuratorDeposit = BountyCuratorDeposit; + 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; } -parameter_types! { - pub const ChildBountyCuratorDepositBase: Permill = Permill::from_percent(10); -} impl pallet_child_bounties::Config for Test { type Event = Event; type MaxActiveChildBountyCount = ConstU32<2>; type ChildBountyValueMinimum = ConstU64<1>; - type ChildBountyCuratorDepositBase = ChildBountyCuratorDepositBase; type WeightInfo = (); } @@ -197,10 +201,10 @@ fn minting_works() { fn add_child_bounty() { new_test_ext().execute_with(|| { // TestProcedure. - // 1, Create bounty & move to active state with enough bounty fund & master-curator. - // 2, Master-curator adds child-bounty child-bounty-1, test for error like RequireCurator + // 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, Master-curator adds child-bounty child-bounty-1, moves to "Approved" state & + // 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, @@ -217,27 +221,30 @@ fn add_child_bounty() { System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); + let fee = 8; + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, fee)); Balances::make_free_balance_be(&4, 10); assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); - assert_eq!(Balances::free_balance(&4), 8); - assert_eq!(Balances::reserved_balance(&4), 2); + // 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 master curator. + // Acc-4 is the parent curator. // Call from invalid origin & check for error "RequireCurator". assert_noop!( ChildBounties::add_child_bounty(Origin::signed(0), 0, 10, b"12345-p1".to_vec()), BountiesError::RequireCurator, ); - // Update the master curator balance. + // Update the parent curator balance. Balances::make_free_balance_be(&4, 101); - // Master curator fee is reserved on parent bounty account. + // 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); @@ -258,7 +265,7 @@ fn add_child_bounty() { assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); assert_eq!(Balances::free_balance(4), 101); - assert_eq!(Balances::reserved_balance(4), 2); + assert_eq!(Balances::reserved_balance(4), expected_deposit); // DB check. // Check the child-bounty status. @@ -285,8 +292,8 @@ fn add_child_bounty() { fn child_bounty_assign_curator() { new_test_ext().execute_with(|| { // TestProcedure - // 1, Create bounty & move to active state with enough bounty fund & master-curator. - // 2, Master-curator adds child-bounty child-bounty-1, moves to "Active" state. + // 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. @@ -302,21 +309,22 @@ fn child_bounty_assign_curator() { System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); - + let fee = 4; + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, fee)); assert_ok!(Bounties::accept_curator(Origin::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 master curator. - // Curator deposit is reserved for master curator on parent bounty. - assert_eq!(Balances::free_balance(4), 99); - assert_eq!(Balances::reserved_balance(4), 2); + // 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 master curator & make sure enough deposit. + // Acc-4 is the parent curator & make sure enough deposit. assert_ok!(ChildBounties::add_child_bounty(Origin::signed(4), 0, 10, b"12345-p1".to_vec())); assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); @@ -329,22 +337,23 @@ fn child_bounty_assign_curator() { assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 10); assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0); - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, 2)); + let fee = 6u64; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, fee)); assert_eq!( ChildBounties::child_bounties(0, 0).unwrap(), ChildBounty { parent_bounty: 0, value: 10, - fee: 2, + fee, curator_deposit: 0, status: ChildBountyStatus::CuratorProposed { curator: 8 }, } ); - // Check the balance of master curator. - assert_eq!(Balances::free_balance(4), 99); - assert_eq!(Balances::reserved_balance(4), 2); + // 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(Origin::signed(3), 0, 0), @@ -353,20 +362,22 @@ fn child_bounty_assign_curator() { assert_ok!(ChildBounties::accept_curator(Origin::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: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::Active { curator: 8 }, } ); - // Deposit for child-bounty curator is reserved. - assert_eq!(Balances::free_balance(8), 100); - assert_eq!(Balances::reserved_balance(8), 1); + // 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); @@ -411,7 +422,8 @@ fn award_claim_child_bounty() { assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); // Propose and accept curator for child-bounty. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, 2)); + let fee = 8; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, fee)); assert_ok!(ChildBounties::accept_curator(Origin::signed(8), 0, 0)); // Award child-bounty. @@ -423,13 +435,14 @@ fn award_claim_child_bounty() { assert_ok!(ChildBounties::award_child_bounty(Origin::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: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_deposit, status: ChildBountyStatus::PendingPayout { curator: 8, beneficiary: 7, @@ -450,11 +463,11 @@ fn award_claim_child_bounty() { assert_ok!(ChildBounties::claim_child_bounty(Origin::signed(7), 0, 0)); // Ensure child-bounty curator is paid with curator fee & deposit refund. - assert_eq!(Balances::free_balance(8), 103); + 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), 8); + assert_eq!(Balances::free_balance(7), 10 - fee); assert_eq!(Balances::reserved_balance(7), 0); // Child-bounty account status. @@ -591,8 +604,8 @@ fn close_child_bounty_pending() { System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 6)); - + let parent_fee = 6; + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, parent_fee)); assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); // Child-bounty. @@ -601,8 +614,10 @@ fn close_child_bounty_pending() { assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); // Propose and accept curator for child-bounty. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, 2)); + let child_fee = 4; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, child_fee)); assert_ok!(ChildBounties::accept_curator(Origin::signed(8), 0, 0)); + let expected_child_deposit = CuratorDepositMin::get(); assert_ok!(ChildBounties::award_child_bounty(Origin::signed(8), 0, 0, 7)); @@ -616,8 +631,8 @@ fn close_child_bounty_pending() { assert_eq!(ChildBounties::parent_child_bounties(0), 1); // Ensure no changes in child-bounty curator balance. - assert_eq!(Balances::free_balance(8), 100); - assert_eq!(Balances::reserved_balance(8), 1); + 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); @@ -755,7 +770,6 @@ fn child_bounty_active_unassign_curator() { >::on_initialize(2); assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 6)); - assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); // Create Child-bounty. @@ -766,16 +780,18 @@ fn child_bounty_active_unassign_curator() { >::on_initialize(3); // Propose and accept curator for child-bounty. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, 2)); + let fee = 6; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, fee)); assert_ok!(ChildBounties::accept_curator(Origin::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: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::Active { curator: 8 }, } ); @@ -792,27 +808,29 @@ fn child_bounty_active_unassign_curator() { ChildBounty { parent_bounty: 0, value: 10, - fee: 2, + fee, curator_deposit: 0, status: ChildBountyStatus::Added, } ); // Ensure child-bounty curator was slashed. - assert_eq!(Balances::free_balance(8), 100); + 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. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 7, 2)); + let fee = 2; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 7, fee)); assert_ok!(ChildBounties::accept_curator(Origin::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: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::Active { curator: 7 }, } ); @@ -820,7 +838,7 @@ fn child_bounty_active_unassign_curator() { System::set_block_number(5); >::on_initialize(5); - // Unassign curator again - from master curator. + // Unassign curator again - from parent curator. assert_ok!(ChildBounties::unassign_curator(Origin::signed(4), 0, 0)); // Verify updated child-bounty status. @@ -836,7 +854,7 @@ fn child_bounty_active_unassign_curator() { ); // Ensure child-bounty curator was slashed. - assert_eq!(Balances::free_balance(7), 100); + 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. @@ -848,8 +866,8 @@ fn child_bounty_active_unassign_curator() { ChildBounty { parent_bounty: 0, value: 10, - fee: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::Active { curator: 6 }, } ); @@ -877,16 +895,18 @@ fn child_bounty_active_unassign_curator() { assert_eq!(Balances::reserved_balance(6), 0); // Propose and accept curator for child-bounty one last time. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 6, 2)); + let fee = 2; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 6, fee)); assert_ok!(ChildBounties::accept_curator(Origin::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: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::Active { curator: 6 }, } ); @@ -920,7 +940,7 @@ fn child_bounty_active_unassign_curator() { ); // Ensure child-bounty curator was slashed. - assert_eq!(Balances::free_balance(6), 100); // slashed + assert_eq!(Balances::free_balance(6), 101 - expected_child_deposit); // slashed assert_eq!(Balances::reserved_balance(6), 0); }); } @@ -960,16 +980,18 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { >::on_initialize(3); // Propose and accept curator for child-bounty. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, 2)); + let fee = 8; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, fee)); assert_ok!(ChildBounties::accept_curator(Origin::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: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::Active { curator: 8 }, } ); @@ -999,14 +1021,14 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { ChildBounty { parent_bounty: 0, value: 10, - fee: 2, + fee, curator_deposit: 0, status: ChildBountyStatus::Added, } ); // Ensure child-bounty curator was slashed. - assert_eq!(Balances::free_balance(8), 100); + assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit); assert_eq!(Balances::reserved_balance(8), 0); // slashed System::set_block_number(6); @@ -1020,16 +1042,18 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { >::on_initialize(7); // Propose and accept curator for child-bounty again. - assert_ok!(ChildBounties::propose_curator(Origin::signed(5), 0, 0, 7, 2)); + let fee = 2; + assert_ok!(ChildBounties::propose_curator(Origin::signed(5), 0, 0, 7, fee)); assert_ok!(ChildBounties::accept_curator(Origin::signed(7), 0, 0)); + let expected_deposit = CuratorDepositMin::get(); assert_eq!( ChildBounties::child_bounties(0, 0).unwrap(), ChildBounty { parent_bounty: 0, value: 10, - fee: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_deposit, status: ChildBountyStatus::Active { curator: 7 }, } ); @@ -1048,7 +1072,7 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { System::set_block_number(9); >::on_initialize(9); - // Unassign curator again - from master curator. + // Unassign curator again - from parent curator. assert_ok!(ChildBounties::unassign_curator(Origin::signed(7), 0, 0)); // Verify updated child-bounty status. @@ -1157,25 +1181,26 @@ fn children_curator_fee_calculation_test() { System::set_block_number(4); >::on_initialize(4); - // Propose curator for child-bounty. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, 2)); + let fee = 6; + // Propose curator for child-bounty. + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, fee)); // Check curator fee added to the sum. - assert_eq!(ChildBounties::children_curator_fees(0), 2); - + assert_eq!(ChildBounties::children_curator_fees(0), fee); // Accept curator for child-bounty. assert_ok!(ChildBounties::accept_curator(Origin::signed(8), 0, 0)); - // Award child-bounty. assert_ok!(ChildBounties::award_child_bounty(Origin::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: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::PendingPayout { curator: 8, beneficiary: 7, @@ -1201,7 +1226,7 @@ fn children_curator_fee_calculation_test() { assert_ok!(Bounties::claim_bounty(Origin::signed(9), 0)); // Ensure parent-bounty curator received correctly reduced fee. - assert_eq!(Balances::free_balance(4), 105); // 101 + 6 - 2 + assert_eq!(Balances::free_balance(4), 101 + 6 - fee); // 101 + 6 - 2 assert_eq!(Balances::reserved_balance(4), 0); // Verify parent-bounty beneficiary balance. @@ -1209,3 +1234,176 @@ fn children_curator_fee_calculation_test() { 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( + Origin::signed(parent_curator), + parent_value, + b"12345".to_vec() + )); + assert_ok!(Bounties::approve_bounty(Origin::root(), parent_index)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator( + Origin::root(), + parent_index, + parent_curator, + parent_fee + )); + assert_ok!(Bounties::accept_curator(Origin::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( + Origin::signed(parent_curator), + parent_index, + child_value, + b"12345-p1".to_vec() + )); + System::set_block_number(3); + >::on_initialize(3); + assert_ok!(ChildBounties::propose_curator( + Origin::signed(parent_curator), + parent_index, + child_index, + child_curator, + child_fee + )); + assert_ok!(ChildBounties::accept_curator( + Origin::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( + Origin::signed(parent_curator), + parent_index, + child_value, + b"12345-p1".to_vec() + )); + System::set_block_number(4); + >::on_initialize(4); + assert_ok!(ChildBounties::propose_curator( + Origin::signed(parent_curator), + parent_index, + child_index, + child_curator, + child_fee + )); + assert_ok!(ChildBounties::accept_curator( + Origin::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( + Origin::signed(parent_curator), + parent_index, + child_value, + b"12345-p1".to_vec() + )); + System::set_block_number(5); + >::on_initialize(5); + assert_ok!(ChildBounties::propose_curator( + Origin::signed(parent_curator), + parent_index, + child_index, + child_curator, + child_fee + )); + assert_ok!(ChildBounties::accept_curator( + Origin::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( + Origin::signed(parent_curator), + parent_index, + child_value, + b"12345-p1".to_vec() + )); + System::set_block_number(5); + >::on_initialize(5); + assert_ok!(ChildBounties::propose_curator( + Origin::signed(parent_curator), + parent_index, + child_index, + child_curator, + child_fee + )); + assert_ok!(ChildBounties::accept_curator( + Origin::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); + }); +}