// This file is part of Substrate. // Copyright (C) 2020-2021 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. //! bounties pallet tests. #![cfg(test)] use super::*; use crate as pallet_bounties; use std::cell::RefCell; use frame_support::{ assert_noop, assert_ok, pallet_prelude::GenesisBuild, parameter_types, traits::OnInitialize, weights::Weight, PalletId, }; use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BadOrigin, BlakeTwo256, IdentityLookup}, Perbill, }; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( pub enum Test where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Bounties: pallet_bounties::{Pallet, Call, Storage, Event}, Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, } ); parameter_types! { pub const BlockHashCount: u64 = 250; pub const MaximumBlockWeight: Weight = 1024; pub const MaximumBlockLength: u32 = 2 * 1024; pub const AvailableBlockRatio: Perbill = Perbill::one(); } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = (); type Origin = Origin; type Index = u64; type BlockNumber = u64; type Call = Call; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account type Lookup = IdentityLookup; type Header = Header; type Event = Event; type BlockHashCount = BlockHashCount; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); } parameter_types! { pub const ExistentialDeposit: u64 = 1; } impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u64; type Event = Event; type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); } thread_local! { static TEN_TO_FOURTEEN: RefCell> = RefCell::new(vec![10,11,12,13,14]); } parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); pub const ProposalBondMinimum: u64 = 1; pub const SpendPeriod: u64 = 2; pub const Burn: Permill = Permill::from_percent(50); pub const DataDepositPerByte: u64 = 1; pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); pub const MaxApprovals: u32 = 100; } // impl pallet_treasury::Config for Test { impl pallet_treasury::Config for Test { type PalletId = TreasuryPalletId; type Currency = pallet_balances::Pallet; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; type Event = Event; type OnSlash = (); type ProposalBond = ProposalBond; type ProposalBondMinimum = ProposalBondMinimum; type SpendPeriod = SpendPeriod; type Burn = Burn; type BurnDestination = (); // Just gets burned. type WeightInfo = (); type SpendFunds = Bounties; type MaxApprovals = MaxApprovals; } parameter_types! { pub const BountyDepositBase: u64 = 80; pub const BountyDepositPayoutDelay: u64 = 3; pub const BountyUpdatePeriod: u32 = 20; pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); pub const BountyValueMinimum: u64 = 1; pub const MaximumReasonLength: u32 = 16384; } impl Config for Test { type Event = Event; type BountyDepositBase = BountyDepositBase; type BountyDepositPayoutDelay = BountyDepositPayoutDelay; type BountyUpdatePeriod = BountyUpdatePeriod; type BountyCuratorDeposit = BountyCuratorDeposit; type BountyValueMinimum = BountyValueMinimum; type DataDepositPerByte = DataDepositPerByte; type MaximumReasonLength = MaximumReasonLength; type WeightInfo = (); } type TreasuryError = pallet_treasury::Error; pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized at ED. balances: vec![(0, 100), (1, 98), (2, 1)], } .assimilate_storage(&mut t) .unwrap(); GenesisBuild::::assimilate_storage(&pallet_treasury::GenesisConfig, &mut t).unwrap(); t.into() } fn last_event() -> RawEvent { System::events() .into_iter() .map(|r| r.event) .filter_map(|e| if let Event::Bounties(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 spend_proposal_takes_min_deposit() { new_test_ext().execute_with(|| { assert_ok!(Treasury::propose_spend(Origin::signed(0), 1, 3)); assert_eq!(Balances::free_balance(0), 99); assert_eq!(Balances::reserved_balance(0), 1); }); } #[test] fn spend_proposal_takes_proportional_deposit() { new_test_ext().execute_with(|| { assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); assert_eq!(Balances::free_balance(0), 95); assert_eq!(Balances::reserved_balance(0), 5); }); } #[test] fn spend_proposal_fails_when_proposer_poor() { new_test_ext().execute_with(|| { assert_noop!( Treasury::propose_spend(Origin::signed(2), 100, 3), TreasuryError::InsufficientProposersBalance, ); }); } #[test] fn accepted_spend_proposal_ignored_outside_spend_period() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); >::on_initialize(1); assert_eq!(Balances::free_balance(3), 0); assert_eq!(Treasury::pot(), 100); }); } #[test] fn unused_pot_should_diminish() { new_test_ext().execute_with(|| { let init_total_issuance = Balances::total_issuance(); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Balances::total_issuance(), init_total_issuance + 100); >::on_initialize(2); assert_eq!(Treasury::pot(), 50); assert_eq!(Balances::total_issuance(), init_total_issuance + 50); }); } #[test] fn rejected_spend_proposal_ignored_on_spend_period() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); assert_ok!(Treasury::reject_proposal(Origin::root(), 0)); >::on_initialize(2); assert_eq!(Balances::free_balance(3), 0); assert_eq!(Treasury::pot(), 50); }); } #[test] fn reject_already_rejected_spend_proposal_fails() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); assert_ok!(Treasury::reject_proposal(Origin::root(), 0)); assert_noop!(Treasury::reject_proposal(Origin::root(), 0), TreasuryError::InvalidIndex); }); } #[test] fn reject_non_existent_spend_proposal_fails() { new_test_ext().execute_with(|| { assert_noop!( Treasury::reject_proposal(Origin::root(), 0), pallet_treasury::Error::::InvalidIndex ); }); } #[test] fn accept_non_existent_spend_proposal_fails() { new_test_ext().execute_with(|| { assert_noop!(Treasury::approve_proposal(Origin::root(), 0), TreasuryError::InvalidIndex); }); } #[test] fn accept_already_rejected_spend_proposal_fails() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); assert_ok!(Treasury::reject_proposal(Origin::root(), 0)); assert_noop!(Treasury::approve_proposal(Origin::root(), 0), TreasuryError::InvalidIndex); }); } #[test] fn accepted_spend_proposal_enacted_on_spend_period() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); >::on_initialize(2); assert_eq!(Balances::free_balance(3), 100); assert_eq!(Treasury::pot(), 0); }); } #[test] fn pot_underflow_should_not_diminish() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); assert_ok!(Treasury::propose_spend(Origin::signed(0), 150, 3)); assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); >::on_initialize(2); assert_eq!(Treasury::pot(), 100); // Pot hasn't changed assert_ok!(Balances::deposit_into_existing(&Treasury::account_id(), 100)); >::on_initialize(4); assert_eq!(Balances::free_balance(3), 150); // Fund has been spent assert_eq!(Treasury::pot(), 25); // Pot has finally changed }); } // Treasury account doesn't get deleted if amount approved to spend is all its free balance. // i.e. pot should not include existential deposit needed for account survival. #[test] fn treasury_account_doesnt_get_deleted() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); let treasury_balance = Balances::free_balance(&Treasury::account_id()); assert_ok!(Treasury::propose_spend(Origin::signed(0), treasury_balance, 3)); assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); >::on_initialize(2); assert_eq!(Treasury::pot(), 100); // Pot hasn't changed assert_ok!(Treasury::propose_spend(Origin::signed(0), Treasury::pot(), 3)); assert_ok!(Treasury::approve_proposal(Origin::root(), 1)); >::on_initialize(4); assert_eq!(Treasury::pot(), 0); // Pot is emptied assert_eq!(Balances::free_balance(Treasury::account_id()), 1); // but the account is still there }); } // In case treasury account is not existing then it works fine. // This is useful for chain that will just update runtime. #[test] fn inexistent_account_works() { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(0, 100), (1, 99), (2, 1)] } .assimilate_storage(&mut t) .unwrap(); // Treasury genesis config is not build thus treasury account does not exist let mut t: sp_io::TestExternalities = t.into(); t.execute_with(|| { assert_eq!(Balances::free_balance(Treasury::account_id()), 0); // Account does not exist assert_eq!(Treasury::pot(), 0); // Pot is empty assert_ok!(Treasury::propose_spend(Origin::signed(0), 99, 3)); assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); assert_ok!(Treasury::propose_spend(Origin::signed(0), 1, 3)); assert_ok!(Treasury::approve_proposal(Origin::root(), 1)); >::on_initialize(2); assert_eq!(Treasury::pot(), 0); // Pot hasn't changed assert_eq!(Balances::free_balance(3), 0); // Balance of `3` hasn't changed Balances::make_free_balance_be(&Treasury::account_id(), 100); assert_eq!(Treasury::pot(), 99); // Pot now contains funds assert_eq!(Balances::free_balance(Treasury::account_id()), 100); // Account does exist >::on_initialize(4); assert_eq!(Treasury::pot(), 0); // Pot has changed assert_eq!(Balances::free_balance(3), 99); // Balance of `3` has changed }); } #[test] fn propose_bounty_works() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); assert_ok!(Bounties::propose_bounty(Origin::signed(0), 10, b"1234567890".to_vec())); assert_eq!(last_event(), RawEvent::BountyProposed(0)); let deposit: u64 = 85 + 5; assert_eq!(Balances::reserved_balance(0), deposit); assert_eq!(Balances::free_balance(0), 100 - deposit); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, fee: 0, curator_deposit: 0, value: 10, bond: deposit, status: BountyStatus::Proposed, } ); assert_eq!(Bounties::bounty_descriptions(0).unwrap(), b"1234567890".to_vec()); assert_eq!(Bounties::bounty_count(), 1); }); } #[test] fn propose_bounty_validation_works() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); assert_noop!( Bounties::propose_bounty(Origin::signed(1), 0, [0; 17_000].to_vec()), Error::::ReasonTooBig ); assert_noop!( Bounties::propose_bounty(Origin::signed(1), 10, b"12345678901234567890".to_vec()), Error::::InsufficientProposersBalance ); assert_noop!( Bounties::propose_bounty(Origin::signed(1), 0, b"12345678901234567890".to_vec()), Error::::InvalidValue ); }); } #[test] fn close_bounty_works() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_noop!(Bounties::close_bounty(Origin::root(), 0), Error::::InvalidIndex); assert_ok!(Bounties::propose_bounty(Origin::signed(0), 10, b"12345".to_vec())); assert_ok!(Bounties::close_bounty(Origin::root(), 0)); let deposit: u64 = 80 + 5; assert_eq!(last_event(), RawEvent::BountyRejected(0, deposit)); assert_eq!(Balances::reserved_balance(0), 0); assert_eq!(Balances::free_balance(0), 100 - deposit); assert_eq!(Bounties::bounties(0), None); assert!(!pallet_treasury::Proposals::::contains_key(0)); assert_eq!(Bounties::bounty_descriptions(0), None); }); } #[test] fn approve_bounty_works() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_noop!(Bounties::approve_bounty(Origin::root(), 0), Error::::InvalidIndex); assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); let deposit: u64 = 80 + 5; assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, fee: 0, value: 50, curator_deposit: 0, bond: deposit, status: BountyStatus::Approved, } ); assert_eq!(Bounties::bounty_approvals(), vec![0]); assert_noop!(Bounties::close_bounty(Origin::root(), 0), Error::::UnexpectedStatus); // deposit not returned yet assert_eq!(Balances::reserved_balance(0), deposit); assert_eq!(Balances::free_balance(0), 100 - deposit); >::on_initialize(2); // return deposit assert_eq!(Balances::reserved_balance(0), 0); assert_eq!(Balances::free_balance(0), 100); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, fee: 0, curator_deposit: 0, value: 50, bond: deposit, status: BountyStatus::Funded, } ); assert_eq!(Treasury::pot(), 100 - 50 - 25); // burn 25 assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 50); }); } #[test] fn assign_curator_works() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_noop!( Bounties::propose_curator(Origin::root(), 0, 4, 4), Error::::InvalidIndex ); assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); System::set_block_number(2); >::on_initialize(2); assert_noop!( Bounties::propose_curator(Origin::root(), 0, 4, 50), Error::::InvalidFee ); assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, fee: 4, curator_deposit: 0, value: 50, bond: 85, status: BountyStatus::CuratorProposed { curator: 4 }, } ); assert_noop!(Bounties::accept_curator(Origin::signed(1), 0), Error::::RequireCurator); assert_noop!( Bounties::accept_curator(Origin::signed(4), 0), pallet_balances::Error::::InsufficientBalance ); Balances::make_free_balance_be(&4, 10); assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, fee: 4, curator_deposit: 2, 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); }); } #[test] fn unassign_curator_works() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); System::set_block_number(2); >::on_initialize(2); assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); 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, curator_deposit: 0, value: 50, bond: 85, status: BountyStatus::Funded, } ); assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); Balances::make_free_balance_be(&4, 10); assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); assert_ok!(Bounties::unassign_curator(Origin::root(), 0)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, fee: 4, curator_deposit: 0, value: 50, bond: 85, status: BountyStatus::Funded, } ); assert_eq!(Balances::free_balance(&4), 8); assert_eq!(Balances::reserved_balance(&4), 0); // slashed 2 }); } #[test] fn award_and_claim_bounty_works() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); Balances::make_free_balance_be(&4, 10); assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); System::set_block_number(2); >::on_initialize(2); assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); assert_eq!(Balances::free_balance(4), 8); // inital 10 - 2 deposit assert_noop!( Bounties::award_bounty(Origin::signed(1), 0, 3), Error::::RequireCurator ); assert_ok!(Bounties::award_bounty(Origin::signed(4), 0, 3)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, fee: 4, curator_deposit: 2, value: 50, bond: 85, status: BountyStatus::PendingPayout { curator: 4, beneficiary: 3, unlock_at: 5 }, } ); assert_noop!(Bounties::claim_bounty(Origin::signed(1), 0), Error::::Premature); System::set_block_number(5); >::on_initialize(5); assert_ok!(Balances::transfer(Origin::signed(0), Bounties::bounty_account_id(0), 10)); assert_ok!(Bounties::claim_bounty(Origin::signed(1), 0)); assert_eq!(last_event(), RawEvent::BountyClaimed(0, 56, 3)); assert_eq!(Balances::free_balance(4), 14); // initial 10 + fee 4 assert_eq!(Balances::free_balance(3), 56); assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 0); assert_eq!(Bounties::bounties(0), None); assert_eq!(Bounties::bounty_descriptions(0), None); }); } #[test] fn claim_handles_high_fee() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); Balances::make_free_balance_be(&4, 30); assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); System::set_block_number(2); >::on_initialize(2); assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 49)); assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); assert_ok!(Bounties::award_bounty(Origin::signed(4), 0, 3)); System::set_block_number(5); >::on_initialize(5); // make fee > balance let res = Balances::slash(&Bounties::bounty_account_id(0), 10); assert_eq!(res.0.peek(), 10); assert_ok!(Bounties::claim_bounty(Origin::signed(1), 0)); assert_eq!(last_event(), RawEvent::BountyClaimed(0, 0, 3)); assert_eq!(Balances::free_balance(4), 70); // 30 + 50 - 10 assert_eq!(Balances::free_balance(3), 0); assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 0); assert_eq!(Bounties::bounties(0), None); assert_eq!(Bounties::bounty_descriptions(0), None); }); } #[test] fn cancel_and_refund() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); System::set_block_number(2); >::on_initialize(2); assert_ok!(Balances::transfer(Origin::signed(0), Bounties::bounty_account_id(0), 10)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, fee: 0, curator_deposit: 0, value: 50, bond: 85, status: BountyStatus::Funded, } ); assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 60); assert_noop!(Bounties::close_bounty(Origin::signed(0), 0), BadOrigin); assert_ok!(Bounties::close_bounty(Origin::root(), 0)); assert_eq!(Treasury::pot(), 85); // - 25 + 10 }); } #[test] fn award_and_cancel() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); System::set_block_number(2); >::on_initialize(2); assert_ok!(Bounties::propose_curator(Origin::root(), 0, 0, 10)); assert_ok!(Bounties::accept_curator(Origin::signed(0), 0)); assert_eq!(Balances::free_balance(0), 95); assert_eq!(Balances::reserved_balance(0), 5); assert_ok!(Bounties::award_bounty(Origin::signed(0), 0, 3)); // Cannot close bounty directly when payout is happening... assert_noop!(Bounties::close_bounty(Origin::root(), 0), Error::::PendingPayout); // Instead unassign the curator to slash them and then close. assert_ok!(Bounties::unassign_curator(Origin::root(), 0)); assert_ok!(Bounties::close_bounty(Origin::root(), 0)); assert_eq!(last_event(), RawEvent::BountyCanceled(0)); assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 0); // Slashed. assert_eq!(Balances::free_balance(0), 95); assert_eq!(Balances::reserved_balance(0), 0); assert_eq!(Bounties::bounties(0), None); assert_eq!(Bounties::bounty_descriptions(0), None); }); } #[test] fn expire_and_unassign() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); System::set_block_number(2); >::on_initialize(2); assert_ok!(Bounties::propose_curator(Origin::root(), 0, 1, 10)); assert_ok!(Bounties::accept_curator(Origin::signed(1), 0)); assert_eq!(Balances::free_balance(1), 93); assert_eq!(Balances::reserved_balance(1), 5); System::set_block_number(22); >::on_initialize(22); assert_noop!(Bounties::unassign_curator(Origin::signed(0), 0), Error::::Premature); System::set_block_number(23); >::on_initialize(23); assert_ok!(Bounties::unassign_curator(Origin::signed(0), 0)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, fee: 10, curator_deposit: 0, value: 50, bond: 85, status: BountyStatus::Funded, } ); assert_eq!(Balances::free_balance(1), 93); assert_eq!(Balances::reserved_balance(1), 0); // slashed }); } #[test] fn extend_expiry() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); Balances::make_free_balance_be(&4, 10); assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); assert_noop!( Bounties::extend_bounty_expiry(Origin::signed(1), 0, Vec::new()), Error::::UnexpectedStatus ); System::set_block_number(2); >::on_initialize(2); assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 10)); assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); assert_eq!(Balances::free_balance(4), 5); assert_eq!(Balances::reserved_balance(4), 5); System::set_block_number(10); >::on_initialize(10); assert_noop!( Bounties::extend_bounty_expiry(Origin::signed(0), 0, Vec::new()), Error::::RequireCurator ); assert_ok!(Bounties::extend_bounty_expiry(Origin::signed(4), 0, Vec::new())); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, fee: 10, curator_deposit: 5, value: 50, bond: 85, status: BountyStatus::Active { curator: 4, update_due: 30 }, } ); assert_ok!(Bounties::extend_bounty_expiry(Origin::signed(4), 0, Vec::new())); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, fee: 10, curator_deposit: 5, value: 50, bond: 85, status: BountyStatus::Active { curator: 4, update_due: 30 }, // still the same } ); System::set_block_number(25); >::on_initialize(25); assert_noop!(Bounties::unassign_curator(Origin::signed(0), 0), Error::::Premature); assert_ok!(Bounties::unassign_curator(Origin::signed(4), 0)); assert_eq!(Balances::free_balance(4), 10); // not slashed assert_eq!(Balances::reserved_balance(4), 0); }); } #[test] fn genesis_funding_works() { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); let initial_funding = 100; pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized with 100. balances: vec![(0, 100), (Treasury::account_id(), initial_funding)], } .assimilate_storage(&mut t) .unwrap(); GenesisBuild::::assimilate_storage(&pallet_treasury::GenesisConfig, &mut t).unwrap(); let mut t: sp_io::TestExternalities = t.into(); t.execute_with(|| { assert_eq!(Balances::free_balance(Treasury::account_id()), initial_funding); assert_eq!(Treasury::pot(), initial_funding - Balances::minimum_balance()); }); }