diff --git a/.maintain/ci/travis.local.sh b/.maintain/ci/travis.local.sh new file mode 100644 index 000000000..5ee3911fd --- /dev/null +++ b/.maintain/ci/travis.local.sh @@ -0,0 +1,17 @@ +readonly TEST_CRATES=( + 'kton' + 'ring' + 'staking' + 'treasury' +); + +function main() { + cargo build + + for crate in ${TEST_CRATES[@]} + do + cargo test -p "darwinia-$crate" + done +} + +main diff --git a/.travis.yml b/.travis.yml index bab8431c2..aaf3886d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,10 @@ jobs: env: RUST_TOOLCHAIN=nightly-2020-02-07 TARGET=native STAKING script: .maintain/ci/darwinia_test_script.sh staking + - stage: Darwinia Test + env: RUST_TOOLCHAIN=nightly-2020-02-07 TARGET=native TREASURY + script: .maintain/ci/darwinia_test_script.sh treasury + # TODO: remove this when overall test case ready allow_failures: - stage: Overall Test diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index efd7376c8..128f44603 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -490,7 +490,8 @@ impl pallet_staking::Trait for Runtime { parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); - pub const ProposalBondMinimum: Balance = 1 * COIN; + pub const RingProposalBondMinimum: Balance = 1 * COIN; + pub const KtonProposalBondMinimum: Balance = 1 * COIN; pub const SpendPeriod: BlockNumber = 1 * DAYS; pub const Burn: Permill = Permill::from_percent(50); } @@ -501,9 +502,11 @@ impl pallet_treasury::Trait for Runtime { type ApproveOrigin = pallet_collective::EnsureMembers<_4, AccountId, CouncilCollective>; type RejectOrigin = pallet_collective::EnsureMembers<_2, AccountId, CouncilCollective>; type Event = Event; - type ProposalRejection = (); + type KtonProposalRejection = (); + type RingProposalRejection = (); type ProposalBond = ProposalBond; - type ProposalBondMinimum = ProposalBondMinimum; + type RingProposalBondMinimum = RingProposalBondMinimum; + type KtonProposalBondMinimum = KtonProposalBondMinimum; type SpendPeriod = SpendPeriod; type Burn = Burn; } diff --git a/frame/balances/README.adoc b/frame/balances/README.adoc new file mode 100644 index 000000000..35c31fc46 --- /dev/null +++ b/frame/balances/README.adoc @@ -0,0 +1,9 @@ +# Balances + +Balances currently contains **RING** and **KTON**. + +**RING** is system token of Darwinia Network, the initial supply before Darwinia network mainnet release is 2 billion. + +**KTON** is the staking and governance credential of Darwinia Network, KTON can **only obtained by locking RING**, the initial supply is 0. + +At present, some **RING** and **KTON** exist in the Ethereum network and the Tron network in the form of **ERC-20** and **TRC-20**. These TOKENs will be transferred to the Darwinia main network by 1:1 cross-chain conversion after the Darwinia main online. diff --git a/frame/balances/kton/README.adoc b/frame/balances/kton/README.adoc new file mode 100644 index 000000000..31ece1e3f --- /dev/null +++ b/frame/balances/kton/README.adoc @@ -0,0 +1,31 @@ +# Kton + +To encourage users to make long term commitments and pledge, users can choose to lock RING for 3 - 36 months in the process of Staking, and the system will offer a KTON token as reward for users participating in Staking. During the committed pledge period, users can not unlock their RING. (Unless pay triple amounts of KTON as penalty) + +As a result, during RING staking process, user can choose to **lock RING for a period to receive KTON**. The initial supply amount of KTON should be zero, yet before Darwinia Mainnet launch, some users have already started locking their RING in Evolution Land, so there will be some KTON supply at the time of mainnet launch. The earliest design to obtain the KTON by locking the RING appears in the Gringotts of Evolution Land. The related introduction can refer to the Gringotts KTON model [5]. + +KTON can be pledged to receive Staking power, so as to participate POS mining as well. User may Staking via pledge KTON, if user take back their staking KTON, then related POS mining is stopped, and it takes 14 days for unpledged KTON to arrive. + +## FAQ + +### What is Kton? + +**KTON** is the staking and governance credential of Darwinia Network, KTON can **only obtained by locking RING**, the initial supply is 0. + +### Where Kton from? + +Users can choose to lock RING for 3 - 36 months in the process of Staking, and the system will offer a KTON token as reward for users participating in Staking. + +### When Kton spends? + +1. Transfer to other accounts. + +2. Burned by slashed. + +### What will happen after slashing? + +Slashed token goes to tresure. + + +--- +(5): https://forum.evolution.land/topics/55 diff --git a/frame/balances/kton/src/lib.rs b/frame/balances/kton/src/lib.rs index 3e05c41ee..753f30f18 100644 --- a/frame/balances/kton/src/lib.rs +++ b/frame/balances/kton/src/lib.rs @@ -120,7 +120,7 @@ decl_error! { } decl_storage! { - trait Store for Module as Balances { + trait Store for Module as KtonBalances { /// The total units issued in the system. pub TotalIssuance get(fn total_issuance) build(|config: &GenesisConfig| { config.balances.iter().fold(Zero::zero(), |acc: T::Balance, &(_, n)| acc + n) diff --git a/frame/balances/ring/README.adoc b/frame/balances/ring/README.adoc new file mode 100644 index 000000000..1554d30aa --- /dev/null +++ b/frame/balances/ring/README.adoc @@ -0,0 +1,3 @@ +# RING + +**RING** is system token of Darwinia Network, the initial supply before Darwinia network mainnet release is 2 billion. diff --git a/frame/balances/ring/src/lib.rs b/frame/balances/ring/src/lib.rs index 924957920..af2512685 100644 --- a/frame/balances/ring/src/lib.rs +++ b/frame/balances/ring/src/lib.rs @@ -325,7 +325,7 @@ decl_error! { } decl_storage! { - trait Store for Module, I: Instance=DefaultInstance> as Balances { + trait Store for Module, I: Instance=DefaultInstance> as RingBalances { /// The total units issued in the system. pub TotalIssuance get(fn total_issuance) build(|config: &GenesisConfig| { config.balances.iter().fold(Zero::zero(), |acc: T::Balance, &(_, n)| acc + n) diff --git a/frame/treasury/README.adoc b/frame/treasury/README.adoc new file mode 100644 index 000000000..e82fb7e84 --- /dev/null +++ b/frame/treasury/README.adoc @@ -0,0 +1,40 @@ +# Treasury Module + +The Treasury module provides a "pot" of funds that can be managed by stakeholders in the +system and a structure for making spending proposals from this pot. + +- `treasury::Trait` +- `Call` + +## Overview + +The Treasury Module itself provides the pot to store funds, and a means for stakeholders to +propose, approve, and deny expenditures. The chain will need to provide a method (e.g. +inflation, fees) for collecting funds. + +By way of example, the Council could vote to fund the Treasury with a portion of the block +reward and use the funds to pay developers. + +### Terminology + +- **Proposal:** A suggestion to allocate funds from the pot to a beneficiary. +- **Beneficiary:** An account who will receive the funds from a proposal iff +the proposal is approved. +- **Deposit:** Funds that a proposer must lock when making a proposal. The +deposit will be returned or slashed if the proposal is approved or rejected +respectively. +- **Pot:** Unspent funds accumulated by the treasury module. + +## Interface + +### Dispatchable Functions + +- `propose_spend` - Make a spending proposal and stake the required deposit. +- `set_pot` - Set the spendable balance of funds. +- `configure` - Configure the module's proposal requirements. +- `reject_proposal` - Reject a proposal, slashing the deposit. +- `approve_proposal` - Accept the proposal, returning the deposit. + +## GenesisConfig + +The Treasury module depends on the `GenesisConfig`. diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 6c16baa60..1456c3c77 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -54,17 +54,17 @@ //! ## GenesisConfig //! //! The Treasury module depends on the [`GenesisConfig`](./struct.GenesisConfig.html). - #![cfg_attr(not(feature = "std"), no_std)] - +#[cfg(test)] +mod tests; mod types { use crate::*; pub type RingBalance = as Currency>>::Balance; pub type RingPositiveImbalance = as Currency>>::PositiveImbalance; pub type RingNegativeImbalance = as Currency>>::NegativeImbalance; - pub type KtonBalance = as Currency>>::Balance; + pub type KtonPositiveImbalance = as Currency>>::PositiveImbalance; pub type KtonNegativeImbalance = as Currency>>::NegativeImbalance; type AccountId = ::AccountId; @@ -72,6 +72,7 @@ mod types { type KtonCurrency = ::KtonCurrency; } +// third-parity use codec::{Decode, Encode}; use frame_support::{ decl_error, decl_event, decl_module, decl_storage, ensure, print, @@ -83,18 +84,20 @@ use frame_system::{self as system, ensure_signed}; use serde::{Deserialize, Serialize}; use sp_runtime::{ traits::{AccountIdConversion, EnsureOrigin, Saturating, StaticLookup, Zero}, - ModuleId, Permill, + ModuleId, Permill, RuntimeDebug, }; use sp_std::prelude::*; +use types::*; +// custom use darwinia_support::OnUnbalancedKton; -use types::*; const MODULE_ID: ModuleId = ModuleId(*b"py/trsry"); pub trait Trait: frame_system::Trait { /// The staking *RING*. type RingCurrency: Currency + ReservableCurrency; + /// The staking *Kton*. type KtonCurrency: Currency + ReservableCurrency; @@ -108,14 +111,16 @@ pub trait Trait: frame_system::Trait { type Event: From> + Into<::Event>; /// Handler for the unbalanced decrease when slashing for a rejected proposal. - type ProposalRejection: OnUnbalanced>; + type RingProposalRejection: OnUnbalanced>; + type KtonProposalRejection: OnUnbalanced>; /// Fraction of a proposal's value that should be bonded in order to place the proposal. /// An accepted proposal gets these back. A rejected proposal does not. type ProposalBond: Get; /// Minimum amount of funds that should be placed in a deposit for making a proposal. - type ProposalBondMinimum: Get>; + type RingProposalBondMinimum: Get>; + type KtonProposalBondMinimum: Get>; /// Period between successive spends. type SpendPeriod: Get; @@ -133,7 +138,8 @@ decl_module! { const ProposalBond: Permill = T::ProposalBond::get(); /// Minimum amount of funds that should be placed in a deposit for making a proposal. - const ProposalBondMinimum: RingBalance = T::ProposalBondMinimum::get(); + const KtonProposalBondMinimum: KtonBalance = T::KtonProposalBondMinimum::get(); + const RingProposalBondMinimum: RingBalance = T::RingProposalBondMinimum::get(); /// Period between successive spends. const SpendPeriod: T::BlockNumber = T::SpendPeriod::get(); @@ -157,19 +163,29 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedNormal(500_000)] fn propose_spend( origin, - #[compact] value: RingBalance, + ring_value: RingBalance, + kton_value: KtonBalance, beneficiary: ::Source ) { let proposer = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; + let (ring_bond, kton_bond) = Self::calculate_bonds(ring_value, kton_value); - let bond = Self::calculate_bond(value); - T::RingCurrency::reserve(&proposer, bond) + T::RingCurrency::reserve(&proposer, ring_bond) + .map_err(|_| >::InsufficientProposersBalance)?; + T::KtonCurrency::reserve(&proposer, kton_bond) .map_err(|_| >::InsufficientProposersBalance)?; let c = Self::proposal_count(); ProposalCount::put(c + 1); - >::insert(c, Proposal { proposer, value, beneficiary, bond }); + >::insert(c, Proposal { + proposer, + beneficiary, + ring_value, + ring_bond, + kton_value, + kton_bond, + }); Self::deposit_event(RawEvent::Proposed(c)); } @@ -185,12 +201,15 @@ decl_module! { fn reject_proposal(origin, #[compact] proposal_id: ProposalIndex) { T::RejectOrigin::ensure_origin(origin)?; let proposal = >::take(&proposal_id).ok_or(>::InvalidProposalIndex)?; + let ring_bond = proposal.ring_bond; + let kton_bond = proposal.kton_bond; + let imbalance_ring = T::RingCurrency::slash_reserved(&proposal.proposer, ring_bond).0; + let imbalance_kton = T::KtonCurrency::slash_reserved(&proposal.proposer, kton_bond).0; - let value = proposal.bond; - let imbalance = T::RingCurrency::slash_reserved(&proposal.proposer, value).0; - T::ProposalRejection::on_unbalanced(imbalance); + T::RingProposalRejection::on_unbalanced(imbalance_ring); + T::KtonProposalRejection::on_unbalanced(imbalance_kton); - Self::deposit_event(Event::::Rejected(proposal_id, value)); + Self::deposit_event(Event::::Rejected(proposal_id, ring_bond, kton_bond)); } /// Approve a proposal. At a later time, the proposal will be allocated to the beneficiary @@ -204,12 +223,11 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedOperational(100_000)] fn approve_proposal(origin, #[compact] proposal_id: ProposalIndex) { T::ApproveOrigin::ensure_origin(origin)?; - ensure!(>::exists(proposal_id), >::InvalidProposalIndex); - Approvals::mutate(|v| v.push(proposal_id)); } + /// This function will implement the `OnFinalize` trait fn on_finalize(n: T::BlockNumber) { // Check to see if we should spend some funds! if (n % T::SpendPeriod::get()).is_zero() { @@ -221,12 +239,14 @@ decl_module! { /// A spending proposal. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Clone, PartialEq, Eq, sp_runtime::RuntimeDebug)] -pub struct Proposal { +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct Proposal { proposer: AccountId, - value: RingBalance, beneficiary: AccountId, - bond: RingBalance, + ring_value: RingBalance, + ring_bond: RingBalance, + kton_value: KtonBalance, + kton_bond: KtonBalance, } decl_storage! { @@ -235,7 +255,7 @@ decl_storage! { ProposalCount get(fn proposal_count): ProposalIndex; /// Proposals that have been made. - Proposals get(fn proposals): map ProposalIndex => Option>>; + Proposals get(fn proposals): map ProposalIndex => Option, KtonBalance>>; /// Proposal indices that have been approved but not yet awarded. Approvals get(fn approvals): Vec; @@ -247,6 +267,11 @@ decl_storage! { &>::account_id(), T::RingCurrency::minimum_balance(), ); + + let _ = T::KtonCurrency::make_free_balance_be( + &>::account_id(), + T::KtonCurrency::minimum_balance(), + ); }); } } @@ -261,15 +286,15 @@ decl_event!( /// New proposal. Proposed(ProposalIndex), /// We have ended a spend period and will now allocate funds. - Spending(RingBalance), + Spending(RingBalance, KtonBalance), /// Some funds have been allocated. - Awarded(ProposalIndex, RingBalance, AccountId), + Awarded(ProposalIndex, RingBalance, KtonBalance, AccountId), /// A proposal was rejected; funds were slashed. - Rejected(ProposalIndex, RingBalance), + Rejected(ProposalIndex, RingBalance, KtonBalance), /// Some of our funds have been burnt. - Burnt(RingBalance), + Burnt(RingBalance, KtonBalance), /// Spending has finished; this is the amount that rolls over until next spend. - Rollover(RingBalance), + Rollover(RingBalance, KtonBalance), /// Some *Ring* have been deposited. DepositRing(RingBalance), /// Some *Kton* have been deposited. @@ -299,49 +324,83 @@ impl Module { } /// The needed bond for a proposal whose spend is `value`. - fn calculate_bond(value: RingBalance) -> RingBalance { - T::ProposalBondMinimum::get().max(T::ProposalBond::get() * value) + fn calculate_bonds(ring: RingBalance, kton: KtonBalance) -> (RingBalance, KtonBalance) { + let mut ring_bond: RingBalance = RingBalance::::from(0); + let mut kton_bond: KtonBalance = KtonBalance::::from(0); + + if ring > ring_bond { + ring_bond = T::RingProposalBondMinimum::get().max(T::ProposalBond::get() * ring); + } + + if kton > kton_bond { + kton_bond = T::KtonProposalBondMinimum::get().max(T::ProposalBond::get() * kton); + } + + (ring_bond, kton_bond) } // Spend some money! fn spend_funds() { - let mut budget_remaining = Self::pot(); - Self::deposit_event(RawEvent::Spending(budget_remaining)); + let mut budget_remaining_ring = Self::pot::(); + let mut budget_remaining_kton = Self::pot::(); + let mut imbalance_ring = >::zero(); + let mut imbalance_kton = >::zero(); + let mut miss_any_ring = false; + let mut miss_any_kton = false; + + Self::deposit_event(RawEvent::Spending(budget_remaining_ring, budget_remaining_kton)); - let mut missed_any = false; - let mut imbalance = >::zero(); Approvals::mutate(|v| { v.retain(|&index| { - // Should always be true, but shouldn't panic if false or we're screwed. - if let Some(p) = Self::proposals(index) { - if p.value <= budget_remaining { - budget_remaining -= p.value; - >::remove(index); - - // return their deposit. - let _ = T::RingCurrency::unreserve(&p.proposer, p.bond); - - // provide the allocation. - imbalance.subsume(T::RingCurrency::deposit_creating(&p.beneficiary, p.value)); - - Self::deposit_event(RawEvent::Awarded(index, p.value, p.beneficiary)); - false - } else { - missed_any = true; - true + // Should always be some, but shouldn't panic if false or we're screwed. + let option_proposal = Self::proposals(index); + if option_proposal.is_none() { + return false; + } + + let p = option_proposal.unwrap(); + if p.ring_value > budget_remaining_ring || p.kton_value > budget_remaining_kton { + if p.ring_value > budget_remaining_ring { + miss_any_ring = true; } - } else { - false + + if p.kton_value > budget_remaining_kton { + miss_any_kton = true; + } + + return true; } + + if p.ring_value <= budget_remaining_ring { + budget_remaining_ring -= p.ring_value; + let _ = T::RingCurrency::unreserve(&p.proposer, p.ring_bond); + imbalance_ring.subsume(T::RingCurrency::deposit_creating(&p.beneficiary, p.ring_value)); + } + + if p.kton_value <= budget_remaining_kton { + budget_remaining_kton -= p.kton_value; + let _ = T::KtonCurrency::unreserve(&p.proposer, p.kton_bond); + imbalance_kton.subsume(T::KtonCurrency::deposit_creating(&p.beneficiary, p.kton_value)); + } + + >::remove(index); + Self::deposit_event(RawEvent::Awarded(index, p.ring_value, p.kton_value, p.beneficiary)); + false }); }); - if !missed_any { + // burn balances + if !miss_any_ring { // burn some proportion of the remaining budget if we run a surplus. - let burn = (T::Burn::get() * budget_remaining).min(budget_remaining); - budget_remaining -= burn; - imbalance.subsume(T::RingCurrency::burn(burn)); - Self::deposit_event(RawEvent::Burnt(burn)) + let burn = (T::Burn::get() * budget_remaining_ring).min(budget_remaining_ring); + budget_remaining_ring -= burn; + imbalance_ring.subsume(T::RingCurrency::burn(burn)); + } + + if !miss_any_kton { + let burn = (T::Burn::get() * budget_remaining_kton).min(budget_remaining_kton); + budget_remaining_kton -= burn; + imbalance_kton.subsume(T::KtonCurrency::burn(burn)); } // Must never be an error, but better to be safe. @@ -350,7 +409,7 @@ impl Module { // Thus account is kept alive; qed; if let Err(problem) = T::RingCurrency::settle( &Self::account_id(), - imbalance, + imbalance_ring, WithdrawReason::Transfer.into(), ExistenceRequirement::KeepAlive, ) { @@ -359,15 +418,29 @@ impl Module { drop(problem); } - Self::deposit_event(RawEvent::Rollover(budget_remaining)); + if let Err(problem) = T::KtonCurrency::settle( + &Self::account_id(), + imbalance_kton, + WithdrawReason::Transfer.into(), + ExistenceRequirement::KeepAlive, + ) { + print("Inconsistent state - couldn't settle imbalance for funds spent by treasury"); + // Nothing else to do here. + drop(problem); + } + + Self::deposit_event(RawEvent::Rollover(budget_remaining_ring, budget_remaining_kton)); } /// Return the amount of money in the pot. // The existential deposit is not part of the pot so treasury account never gets deleted. - fn pot() -> RingBalance { - T::RingCurrency::free_balance(&Self::account_id()) + fn pot() -> C::Balance + where + C: Currency, + { + C::free_balance(&Self::account_id()) // Must never be less than 0 but better be safe. - .saturating_sub(T::RingCurrency::minimum_balance()) + .saturating_sub(C::minimum_balance()) } } @@ -393,340 +466,3 @@ impl OnUnbalancedKton> for Module { Self::deposit_event(RawEvent::DepositKton(numeric_amount)); } } - -#[cfg(test)] -mod tests { - use crate::*; - - use frame_support::{assert_noop, assert_ok, impl_outer_origin, parameter_types, weights::Weight}; - use sp_core::H256; - use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup, OnFinalize}, - Perbill, - }; - - type Ring = darwinia_ring::Module; - type Kton = darwinia_kton::Module; - type Treasury = Module; - - impl_outer_origin! { - pub enum Origin for Test where system = frame_system {} - } - - #[derive(Clone, Eq, PartialEq)] - pub struct Test; - 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::Trait for Test { - type Origin = Origin; - type Call = (); - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = (); - type BlockHashCount = BlockHashCount; - type MaximumBlockWeight = MaximumBlockWeight; - type MaximumBlockLength = MaximumBlockLength; - type AvailableBlockRatio = AvailableBlockRatio; - type Version = (); - type ModuleToIndex = (); - } - parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const TransferFee: u64 = 0; - pub const CreationFee: u64 = 0; - } - impl darwinia_kton::Trait for Test { - type Balance = u64; - type Event = (); - type RingCurrency = Ring; - type TransferPayment = (); - type ExistentialDeposit = ExistentialDeposit; - type TransferFee = TransferFee; - } - impl darwinia_ring::Trait for Test { - type Balance = u64; - type OnFreeBalanceZero = (); - type OnNewAccount = (); - type TransferPayment = (); - type DustRemoval = (); - type Event = (); - type ExistentialDeposit = ExistentialDeposit; - type TransferFee = TransferFee; - type CreationFee = CreationFee; - } - 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); - } - impl Trait for Test { - type RingCurrency = Ring; - type KtonCurrency = Kton; - type ApproveOrigin = frame_system::EnsureRoot; - type RejectOrigin = frame_system::EnsureRoot; - type Event = (); - type ProposalRejection = (); - type ProposalBond = ProposalBond; - type ProposalBondMinimum = ProposalBondMinimum; - type SpendPeriod = SpendPeriod; - type Burn = Burn; - } - - fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let _ = darwinia_ring::GenesisConfig:: { - // Total issuance will be 200 with treasury account initialized at ED. - balances: vec![(0, 100), (1, 98), (2, 1)], - vesting: vec![], - } - .assimilate_storage(&mut t); - let _ = darwinia_kton::GenesisConfig:: { - // Total issuance will be 200 with treasury account initialized at ED. - balances: vec![(0, 100), (1, 98), (2, 1)], - vesting: vec![], - } - .assimilate_storage(&mut t); - let _ = GenesisConfig::default().assimilate_storage::(&mut t); - t.into() - } - - #[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. - Ring::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!(Ring::free_balance(&0), 99); - assert_eq!(Ring::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!(Ring::free_balance(&0), 95); - assert_eq!(Ring::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), - Error::::InsufficientProposersBalance, - ); - }); - } - - #[test] - fn accepted_spend_proposal_ignored_outside_spend_period() { - new_test_ext().execute_with(|| { - Ring::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_finalize(1); - assert_eq!(Ring::free_balance(&3), 0); - assert_eq!(Treasury::pot(), 100); - }); - } - - #[test] - fn unused_pot_should_diminish() { - new_test_ext().execute_with(|| { - let init_total_issuance = Ring::total_issuance(); - Ring::make_free_balance_be(&Treasury::account_id(), 101); - assert_eq!(Ring::total_issuance(), init_total_issuance + 100); - - >::on_finalize(2); - assert_eq!(Treasury::pot(), 50); - assert_eq!(Ring::total_issuance(), init_total_issuance + 50); - }); - } - - #[test] - fn rejected_spend_proposal_ignored_on_spend_period() { - new_test_ext().execute_with(|| { - Ring::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_finalize(2); - assert_eq!(Ring::free_balance(&3), 0); - assert_eq!(Treasury::pot(), 50); - }); - } - - #[test] - fn reject_already_rejected_spend_proposal_fails() { - new_test_ext().execute_with(|| { - Ring::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), - Error::::InvalidProposalIndex - ); - }); - } - - #[test] - fn reject_non_existant_spend_proposal_fails() { - new_test_ext().execute_with(|| { - assert_noop!( - Treasury::reject_proposal(Origin::ROOT, 0), - Error::::InvalidProposalIndex - ); - }); - } - - #[test] - fn accept_non_existant_spend_proposal_fails() { - new_test_ext().execute_with(|| { - assert_noop!( - Treasury::approve_proposal(Origin::ROOT, 0), - Error::::InvalidProposalIndex - ); - }); - } - - #[test] - fn accept_already_rejected_spend_proposal_fails() { - new_test_ext().execute_with(|| { - Ring::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), - Error::::InvalidProposalIndex - ); - }); - } - - #[test] - fn accepted_spend_proposal_enacted_on_spend_period() { - new_test_ext().execute_with(|| { - Ring::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_finalize(2); - assert_eq!(Ring::free_balance(&3), 100); - assert_eq!(Treasury::pot(), 0); - }); - } - - #[test] - fn pot_underflow_should_not_diminish() { - new_test_ext().execute_with(|| { - Ring::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_finalize(2); - assert_eq!(Treasury::pot(), 100); // Pot hasn't changed - - let _ = Ring::deposit_into_existing(&Treasury::account_id(), 100).unwrap(); - >::on_finalize(4); - assert_eq!(Ring::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(|| { - Ring::make_free_balance_be(&Treasury::account_id(), 101); - assert_eq!(Treasury::pot(), 100); - let treasury_balance = Ring::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_finalize(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_finalize(4); - assert_eq!(Treasury::pot(), 0); // Pot is emptied - assert_eq!(Ring::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 usefull for chain that will just update runtime. - #[test] - fn inexisting_account_works() { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - darwinia_ring::GenesisConfig:: { - balances: vec![(0, 100), (1, 99), (2, 1)], - vesting: vec![], - } - .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!(Ring::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_finalize(2); - assert_eq!(Treasury::pot(), 0); // Pot hasn't changed - assert_eq!(Ring::free_balance(&3), 0); // Balance of `3` hasn't changed - - Ring::make_free_balance_be(&Treasury::account_id(), 100); - assert_eq!(Treasury::pot(), 99); // Pot now contains funds - assert_eq!(Ring::free_balance(&Treasury::account_id()), 100); // Account does exist - - >::on_finalize(4); - - assert_eq!(Treasury::pot(), 0); // Pot has changed - assert_eq!(Ring::free_balance(&3), 99); // Balance of `3` has changed - }); - } -} diff --git a/frame/treasury/src/tests.rs b/frame/treasury/src/tests.rs new file mode 100644 index 000000000..2f08a446f --- /dev/null +++ b/frame/treasury/src/tests.rs @@ -0,0 +1,708 @@ +use crate::*; +use frame_support::{assert_noop, assert_ok, impl_outer_origin, parameter_types, weights::Weight}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup, OnFinalize}, + Perbill, +}; + +type Ring = darwinia_ring::Module; +type Kton = darwinia_kton::Module; +type Treasury = Module; + +impl_outer_origin! { + pub enum Origin for Test where system = frame_system {} +} + +#[derive(Clone, Eq, PartialEq)] +pub struct Test; +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::Trait for Test { + type Origin = Origin; + type Call = (); + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); +} +parameter_types! { + pub const ExistentialDeposit: u64 = 1; + pub const TransferFee: u64 = 0; + pub const CreationFee: u64 = 0; +} +impl darwinia_kton::Trait for Test { + type Balance = u64; + type Event = (); + type RingCurrency = Ring; + type TransferPayment = (); + type ExistentialDeposit = ExistentialDeposit; + type TransferFee = TransferFee; +} +impl darwinia_ring::Trait for Test { + type Balance = u64; + type OnFreeBalanceZero = (); + type OnNewAccount = (); + type TransferPayment = (); + type DustRemoval = (); + type Event = (); + type ExistentialDeposit = ExistentialDeposit; + type TransferFee = TransferFee; + type CreationFee = CreationFee; +} +parameter_types! { + pub const ProposalBond: Permill = Permill::from_percent(5); + pub const RingProposalBondMinimum: u64 = 1; + pub const KtonProposalBondMinimum: u64 = 1; + pub const SpendPeriod: u64 = 2; + pub const Burn: Permill = Permill::from_percent(50); +} +impl Trait for Test { + type RingCurrency = Ring; + type KtonCurrency = Kton; + type ApproveOrigin = frame_system::EnsureRoot; + type RejectOrigin = frame_system::EnsureRoot; + type Event = (); + type KtonProposalRejection = (); + type RingProposalRejection = (); + type ProposalBond = ProposalBond; + type RingProposalBondMinimum = RingProposalBondMinimum; + type KtonProposalBondMinimum = KtonProposalBondMinimum; + type SpendPeriod = SpendPeriod; + type Burn = Burn; +} + +fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let _ = darwinia_ring::GenesisConfig:: { + // Total issuance will be 200 with treasury account initialized at ED. + balances: vec![(0, 100), (1, 98), (2, 1)], + vesting: vec![], + } + .assimilate_storage(&mut t); + let _ = darwinia_kton::GenesisConfig:: { + // Total issuance will be 200 with treasury account initialized at ED. + balances: vec![(0, 100), (1, 98), (2, 1)], + vesting: vec![], + } + .assimilate_storage(&mut t); + let _ = GenesisConfig::default().assimilate_storage::(&mut t); + t.into() +} + +#[test] +fn genesis_config_works() { + new_test_ext().execute_with(|| { + assert_eq!(Treasury::pot::(), 0); + 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. + Ring::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Treasury::pot::(), 100); + assert_eq!(Treasury::pot::(), 0); + + // Make sure kton and ring have different storages + Kton::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, 1, 3)); + assert_eq!(Ring::free_balance(&0), 99); + assert_eq!(Kton::free_balance(&0), 99); + assert_eq!(Ring::reserved_balance(&0), 1); + assert_eq!(Kton::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, 100, 3)); + assert_eq!(Ring::free_balance(&0), 95); + assert_eq!(Kton::free_balance(&0), 95); + assert_eq!(Ring::reserved_balance(&0), 5); + assert_eq!(Kton::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, 100, 3), + Error::::InsufficientProposersBalance, + ); + }); +} + +#[test] +fn accepted_spend_proposal_ignored_outside_spend_period() { + new_test_ext().execute_with(|| { + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + + assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 100, 3)); + assert_ok!(Treasury::approve_proposal(Origin::ROOT, 0)); + + >::on_finalize(1); + assert_eq!(Ring::free_balance(&3), 0); + assert_eq!(Kton::free_balance(&3), 0); + assert_eq!(Treasury::pot::(), 100); + assert_eq!(Treasury::pot::(), 100); + }); +} + +#[test] +fn unused_pot_should_diminish() { + new_test_ext().execute_with(|| { + let init_total_ring_issuance = Ring::total_issuance(); + let init_total_kton_issuance = Kton::total_issuance(); + + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + + assert_eq!(Ring::total_issuance(), init_total_ring_issuance + 100); + assert_eq!(Kton::total_issuance(), init_total_kton_issuance + 100); + + >::on_finalize(2); + assert_eq!(Treasury::pot::(), 50); + assert_eq!(Treasury::pot::(), 50); + assert_eq!(Ring::total_issuance(), init_total_ring_issuance + 50); + assert_eq!(Kton::total_issuance(), init_total_kton_issuance + 50); + }); +} + +#[test] +fn rejected_spend_proposal_ignored_on_spend_period() { + new_test_ext().execute_with(|| { + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + + assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 100, 3)); + assert_ok!(Treasury::reject_proposal(Origin::ROOT, 0)); + + >::on_finalize(2); + assert_eq!(Ring::free_balance(&3), 0); + assert_eq!(Kton::free_balance(&3), 0); + assert_eq!(Treasury::pot::(), 50); + assert_eq!(Treasury::pot::(), 50); + }); +} + +#[test] +fn reject_already_rejected_spend_proposal_fails() { + new_test_ext().execute_with(|| { + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + + assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 100, 3)); + assert_ok!(Treasury::reject_proposal(Origin::ROOT, 0)); + assert_noop!( + Treasury::reject_proposal(Origin::ROOT, 0), + Error::::InvalidProposalIndex + ); + }); +} + +#[test] +fn reject_non_existant_spend_proposal_fails() { + new_test_ext().execute_with(|| { + assert_noop!( + Treasury::reject_proposal(Origin::ROOT, 0), + Error::::InvalidProposalIndex + ); + }); +} + +#[test] +fn accept_already_rejected_spend_proposal_fails() { + new_test_ext().execute_with(|| { + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + + assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 100, 3)); + assert_ok!(Treasury::reject_proposal(Origin::ROOT, 0)); + assert_noop!( + Treasury::approve_proposal(Origin::ROOT, 0), + Error::::InvalidProposalIndex + ); + }); +} + +#[test] +fn accepted_spend_proposal_enacted_on_spend_period() { + new_test_ext().execute_with(|| { + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Treasury::pot::(), 100); + assert_eq!(Treasury::pot::(), 100); + + assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 100, 3)); + assert_ok!(Treasury::approve_proposal(Origin::ROOT, 0)); + + >::on_finalize(2); + assert_eq!(Ring::free_balance(&3), 100); + assert_eq!(Kton::free_balance(&3), 100); + assert_eq!(Treasury::pot::(), 0); + assert_eq!(Treasury::pot::(), 0); + }); +} + +#[test] +fn pot_underflow_should_not_diminish() { + new_test_ext().execute_with(|| { + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Treasury::pot::(), 100); + assert_eq!(Treasury::pot::(), 100); + assert_ok!(Treasury::propose_spend(Origin::signed(0), 150, 150, 3)); + assert_ok!(Treasury::approve_proposal(Origin::ROOT, 0)); + + >::on_finalize(2); + assert_eq!(Treasury::pot::(), 100); // Pot hasn't changed + assert_eq!(Treasury::pot::(), 100); // Pot hasn't changed + + let _ = Ring::deposit_into_existing(&Treasury::account_id(), 100).unwrap(); + let _ = Kton::deposit_into_existing(&Treasury::account_id(), 100).unwrap(); + + >::on_finalize(4); + assert_eq!(Ring::free_balance(&3), 150); // Fund has been spent + assert_eq!(Kton::free_balance(&3), 150); // Fund has been spent + assert_eq!(Treasury::pot::(), 25); // Pot has finally changed + 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(|| { + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + + assert_eq!(Treasury::pot::(), 100); + assert_eq!(Treasury::pot::(), 100); + + let ring_treasury_balance = Ring::free_balance(&Treasury::account_id()); + let kton_treasury_balance = Kton::free_balance(&Treasury::account_id()); + + assert_ok!(Treasury::propose_spend( + Origin::signed(0), + ring_treasury_balance, + kton_treasury_balance, + 3 + )); + assert_ok!(Treasury::approve_proposal(Origin::ROOT, 0)); + + >::on_finalize(2); + assert_eq!(Treasury::pot::(), 100); // Pot hasn't changed + assert_eq!(Treasury::pot::(), 100); // Pot hasn't changed + + assert_ok!(Treasury::propose_spend( + Origin::signed(0), + Treasury::pot::(), + Treasury::pot::(), + 3 + )); + assert_ok!(Treasury::approve_proposal(Origin::ROOT, 1)); + + >::on_finalize(4); + assert_eq!(Treasury::pot::(), 0); // Pot is emptied + assert_eq!(Treasury::pot::(), 0); // Pot is emptied + assert_eq!(Ring::free_balance(&Treasury::account_id()), 1); // but the account is still there + assert_eq!(Kton::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 usefull for chain that will just update runtime. +#[test] +fn inexisting_account_works() { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + darwinia_ring::GenesisConfig:: { + balances: vec![(0, 100), (1, 99), (2, 1)], + vesting: vec![], + } + .assimilate_storage(&mut t) + .unwrap(); + + darwinia_kton::GenesisConfig:: { + balances: vec![(0, 100), (1, 99), (2, 1)], + vesting: vec![], + } + .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(|| { + // Account does not exist + assert_eq!(Ring::free_balance(&Treasury::account_id()), 0); + assert_eq!(Kton::free_balance(&Treasury::account_id()), 0); + + // Pot is empty + assert_eq!(Treasury::pot::(), 0); + assert_eq!(Treasury::pot::(), 0); + assert_ok!(Treasury::propose_spend(Origin::signed(0), 99, 99, 3)); + assert_ok!(Treasury::approve_proposal(Origin::ROOT, 0)); + assert_ok!(Treasury::propose_spend(Origin::signed(0), 1, 1, 3)); + assert_ok!(Treasury::approve_proposal(Origin::ROOT, 1)); + + >::on_finalize(2); + // Pot hasn't changed + assert_eq!(Treasury::pot::(), 0); + assert_eq!(Treasury::pot::(), 0); + + // Balance of `3` hasn't changed + assert_eq!(Ring::free_balance(&3), 0); + assert_eq!(Kton::free_balance(&3), 0); + + Ring::make_free_balance_be(&Treasury::account_id(), 100); + Kton::make_free_balance_be(&Treasury::account_id(), 100); + assert_eq!(Treasury::pot::(), 99); // Pot now contains funds + assert_eq!(Treasury::pot::(), 99); // Pot now contains funds + assert_eq!(Ring::free_balance(&Treasury::account_id()), 100); // Account does exist + assert_eq!(Kton::free_balance(&Treasury::account_id()), 100); // Account does exist + + >::on_finalize(4); + + assert_eq!(Treasury::pot::(), 0); // Pot has changed + assert_eq!(Treasury::pot::(), 0); // Pot has changed + assert_eq!(Ring::free_balance(&3), 99); // Balance of `3` has changed + assert_eq!(Kton::free_balance(&3), 99); // Balance of `3` has changed + }); +} + +#[test] +fn no_spent_no_burn() { + // ring + new_test_ext().execute_with(|| { + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + + assert_eq!(Treasury::pot::(), 100); + assert_eq!(Treasury::pot::(), 100); + + assert_ok!(Treasury::propose_spend(Origin::signed(0), 0, 100, 3)); + assert_ok!(Treasury::approve_proposal(Origin::ROOT, 0)); + + >::on_finalize(2); + assert_eq!(Ring::free_balance(&3), 0); + assert_eq!(Kton::free_balance(&3), 100); + assert_eq!(Treasury::pot::(), 50); + assert_eq!(Treasury::pot::(), 0); + }); + + // kton + new_test_ext().execute_with(|| { + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + + assert_eq!(Treasury::pot::(), 100); + assert_eq!(Treasury::pot::(), 100); + + assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 0, 3)); + assert_ok!(Treasury::approve_proposal(Origin::ROOT, 0)); + + >::on_finalize(2); + assert_eq!(Ring::free_balance(&3), 100); + assert_eq!(Kton::free_balance(&3), 0); + assert_eq!(Treasury::pot::(), 0); + assert_eq!(Treasury::pot::(), 50); + }); + + // both + new_test_ext().execute_with(|| { + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + + assert_eq!(Treasury::pot::(), 100); + assert_eq!(Treasury::pot::(), 100); + + assert_ok!(Treasury::propose_spend(Origin::signed(0), 0, 0, 3)); + assert_ok!(Treasury::approve_proposal(Origin::ROOT, 0)); + + >::on_finalize(2); + assert_eq!(Ring::free_balance(&3), 0); + assert_eq!(Kton::free_balance(&3), 0); + assert_eq!(Treasury::pot::(), 50); + assert_eq!(Treasury::pot::(), 50); + }); +} + +/// # Logic Tests. +/// +/// **FIXME**: If some logic went wrong. +/// +/// + Proposal: A suggestion to allocate funds from the pot to a beneficiary. +/// + Beneficiary: An account who will receive the funds from a proposal iff the proposal is approved. +/// + Deposit: Funds that a proposer must lock when making a proposal. +/// The deposit will be returned or slashed if the proposal is approved or rejected respectively. +/// + Pot: Unspent funds accumulated by the treasury module. +#[test] +fn approve_proposal_no_keep_burning() { + new_test_ext().execute_with(|| { + // backtrace init configs. + assert_eq!(Ring::free_balance(&0), 100); + assert_eq!(Kton::free_balance(&0), 100); + assert_eq!(Ring::free_balance(&1), 98); + assert_eq!(Kton::free_balance(&1), 98); + assert_eq!(Ring::free_balance(&2), 1); + assert_eq!(Kton::free_balance(&2), 1); + assert_eq!(Ring::free_balance(&3), 0); + assert_eq!(Kton::free_balance(&3), 0); + assert_eq!(Treasury::pot::(), 0); + assert_eq!(Treasury::pot::(), 0); + + // Ensure an account's free balance equals some value; this will create the account if needed. + // Returns a signed imbalance and status to indicate if the account was successfully updated + // or update has led to killing of the account. + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Treasury::pot::(), 100); + assert_eq!(Treasury::pot::(), 100); + + // Put forward a suggestion for spending, burn treasury balances to AccontID-3 + assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 100, 3)); + assert_ok!(Treasury::approve_proposal(Origin::ROOT, 0)); // Accept proposal + + // @0-1: Check balances after `propose_spend` + >::on_finalize(1); + assert_eq!(Ring::free_balance(&0), 95); // ProposalBond: Permill::from_percent(5); + assert_eq!(Kton::free_balance(&0), 95); // ProposalBond: Permill::from_percent(5); + assert_eq!(Ring::free_balance(&1), 98); // No changes + assert_eq!(Kton::free_balance(&1), 98); // No changes + assert_eq!(Ring::free_balance(&2), 1); // No changes + assert_eq!(Kton::free_balance(&2), 1); // No changes + assert_eq!(Ring::free_balance(&3), 0); // No changes + assert_eq!(Kton::free_balance(&3), 0); // No changes + assert_eq!(Treasury::pot::(), 100); // No changes + assert_eq!(Treasury::pot::(), 100); // No changes + + // @2: On the first spend perid + >::on_finalize(2); // SpendPeriod: u64 = 2; + assert_eq!(Ring::free_balance(&0), 100); // ProposalBond: Permill::from_percent(5); **return bond** + assert_eq!(Kton::free_balance(&0), 100); // ProposalBond: Permill::from_percent(5); **return bond** + assert_eq!(Ring::free_balance(&1), 98); // No changes + assert_eq!(Kton::free_balance(&1), 98); // No changes + assert_eq!(Ring::free_balance(&2), 1); // No changes + assert_eq!(Kton::free_balance(&2), 1); // No changes + assert_eq!(Ring::free_balance(&3), 100); // No changes + assert_eq!(Kton::free_balance(&3), 100); // No changes + assert_eq!(Treasury::pot::(), 0); // Burn: Permill::from_percent(50); **Burn 100 if approve** + assert_eq!(Treasury::pot::(), 0); // Burn: Permill::from_percent(50); **Burn 100 if approve** + + // @3: Check balances on the perid after spend perid + >::on_finalize(3); + assert_eq!(Ring::free_balance(&0), 100); // No changes from last perid + assert_eq!(Kton::free_balance(&0), 100); // No changes from last perid + assert_eq!(Ring::free_balance(&1), 98); // No changes + assert_eq!(Kton::free_balance(&1), 98); // No changes + assert_eq!(Ring::free_balance(&2), 1); // No changes + assert_eq!(Kton::free_balance(&2), 1); // No changes + assert_eq!(Ring::free_balance(&3), 100); // No changes + assert_eq!(Kton::free_balance(&3), 100); // No changes + assert_eq!(Treasury::pot::(), 0); // No changes from last perid + assert_eq!(Treasury::pot::(), 0); // No changes from last perid + + // @4: The second spend perid + >::on_finalize(4); + assert_eq!(Ring::free_balance(&0), 100); // No changes from last perid + assert_eq!(Kton::free_balance(&0), 100); // No changes from last perid + assert_eq!(Ring::free_balance(&1), 98); // No changes + assert_eq!(Kton::free_balance(&1), 98); // No changes + assert_eq!(Ring::free_balance(&2), 1); // No changes + assert_eq!(Kton::free_balance(&2), 1); // No changes + assert_eq!(Ring::free_balance(&3), 100); // No changes + assert_eq!(Kton::free_balance(&3), 100); // No changes + assert_eq!(Treasury::pot::(), 0); // No changes from last perid + assert_eq!(Treasury::pot::(), 0); // No changes from last perid + }); +} + +#[test] +fn reject_proposal_keep_burning() { + new_test_ext().execute_with(|| { + // backtrace init configs. + assert_eq!(Ring::free_balance(&0), 100); + assert_eq!(Kton::free_balance(&0), 100); + assert_eq!(Ring::free_balance(&1), 98); + assert_eq!(Kton::free_balance(&1), 98); + assert_eq!(Ring::free_balance(&2), 1); + assert_eq!(Kton::free_balance(&2), 1); + assert_eq!(Ring::free_balance(&3), 0); + assert_eq!(Kton::free_balance(&3), 0); + assert_eq!(Treasury::pot::(), 0); + assert_eq!(Treasury::pot::(), 0); + + // Ensure an account's free balance equals some value; this will create the account if needed. + // Returns a signed imbalance and status to indicate if the account was successfully updated + // or update has led to killing of the account. + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Treasury::pot::(), 100); + assert_eq!(Treasury::pot::(), 100); + + // Put forward a suggestion for spending, burn treasury balances to AccontID-3 + assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 100, 3)); + assert_ok!(Treasury::reject_proposal(Origin::ROOT, 0)); + + // @0-1: Check balances after `propose_spend` + >::on_finalize(1); + assert_eq!(Ring::free_balance(&0), 95); // ProposalBond: Permill::from_percent(5); + assert_eq!(Kton::free_balance(&0), 95); // ProposalBond: Permill::from_percent(5); + assert_eq!(Ring::free_balance(&1), 98); // No changes + assert_eq!(Kton::free_balance(&1), 98); // No changes + assert_eq!(Ring::free_balance(&2), 1); // No changes + assert_eq!(Kton::free_balance(&2), 1); // No changes + assert_eq!(Ring::free_balance(&3), 0); // No changes + assert_eq!(Kton::free_balance(&3), 0); // No changes + assert_eq!(Treasury::pot::(), 100); // No changes + assert_eq!(Treasury::pot::(), 100); // No changes + + // @2: On the first spend perid + >::on_finalize(2); // SpendPeriod: u64 = 2; + assert_eq!(Ring::free_balance(&0), 95); // ProposalBond: Permill::from_percent(5); + assert_eq!(Kton::free_balance(&0), 95); // ProposalBond: Permill::from_percent(5); + assert_eq!(Ring::free_balance(&1), 98); // No changes + assert_eq!(Kton::free_balance(&1), 98); // No changes + assert_eq!(Ring::free_balance(&2), 1); // No changes + assert_eq!(Kton::free_balance(&2), 1); // No changes + assert_eq!(Ring::free_balance(&3), 0); // No changes + assert_eq!(Kton::free_balance(&3), 0); // No changes + assert_eq!(Treasury::pot::(), 50); // Burn: Permill::from_percent(50); **The Burned Balances just burned?** + assert_eq!(Treasury::pot::(), 50); // Burn: Permill::from_percent(50); **The Burned Balances just burned?** + + // @3: Check balances on the perid after spend perid + >::on_finalize(3); + assert_eq!(Ring::free_balance(&0), 95); // No changes from last perid + assert_eq!(Kton::free_balance(&0), 95); // No changes from last perid + assert_eq!(Ring::free_balance(&1), 98); // No changes + assert_eq!(Kton::free_balance(&1), 98); // No changes + assert_eq!(Ring::free_balance(&2), 1); // No changes + assert_eq!(Kton::free_balance(&2), 1); // No changes + assert_eq!(Ring::free_balance(&3), 0); // No changes + assert_eq!(Kton::free_balance(&3), 0); // No changes + assert_eq!(Treasury::pot::(), 50); // No changes from last perid + assert_eq!(Treasury::pot::(), 50); // No changes from last perid + + // @4: The second spend perid + >::on_finalize(4); + assert_eq!(Ring::free_balance(&0), 95); // No changes from last perid + assert_eq!(Kton::free_balance(&0), 95); // No changes from last perid + assert_eq!(Ring::free_balance(&1), 98); // No changes + assert_eq!(Kton::free_balance(&1), 98); // No changes + assert_eq!(Ring::free_balance(&2), 1); // No changes + assert_eq!(Kton::free_balance(&2), 1); // No changes + assert_eq!(Ring::free_balance(&3), 0); // No changes + assert_eq!(Kton::free_balance(&3), 0); // No changes + assert_eq!(Treasury::pot::(), 25); // No changes from last perid + assert_eq!(Treasury::pot::(), 25); // No changes from last perid + }); +} + +#[test] +fn no_accept_no_reject_keep_burning() { + new_test_ext().execute_with(|| { + // backtrace init configs. + assert_eq!(Ring::free_balance(&0), 100); + assert_eq!(Kton::free_balance(&0), 100); + assert_eq!(Ring::free_balance(&1), 98); + assert_eq!(Kton::free_balance(&1), 98); + assert_eq!(Ring::free_balance(&2), 1); + assert_eq!(Kton::free_balance(&2), 1); + assert_eq!(Ring::free_balance(&3), 0); + assert_eq!(Kton::free_balance(&3), 0); + assert_eq!(Treasury::pot::(), 0); + assert_eq!(Treasury::pot::(), 0); + + // Ensure an account's free balance equals some value; this will create the account if needed. + // Returns a signed imbalance and status to indicate if the account was successfully updated + // or update has led to killing of the account. + Ring::make_free_balance_be(&Treasury::account_id(), 101); + Kton::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Treasury::pot::(), 100); + assert_eq!(Treasury::pot::(), 100); + + // Put forward a suggestion for spending, burn treasury balances to AccontID-3 + assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 100, 3)); + + // @0-1: Check balances after `propose_spend` + >::on_finalize(1); + assert_eq!(Ring::free_balance(&0), 95); // ProposalBond: Permill::from_percent(5); + assert_eq!(Kton::free_balance(&0), 95); // ProposalBond: Permill::from_percent(5); + assert_eq!(Ring::free_balance(&1), 98); // No changes + assert_eq!(Kton::free_balance(&1), 98); // No changes + assert_eq!(Ring::free_balance(&2), 1); // No changes + assert_eq!(Kton::free_balance(&2), 1); // No changes + assert_eq!(Ring::free_balance(&3), 0); // No changes + assert_eq!(Kton::free_balance(&3), 0); // No changes + assert_eq!(Treasury::pot::(), 100); // No changes + assert_eq!(Treasury::pot::(), 100); // No changes + + // @2: On the first spend perid + >::on_finalize(2); // SpendPeriod: u64 = 2; + assert_eq!(Ring::free_balance(&0), 95); // ProposalBond: Permill::from_percent(5); + assert_eq!(Kton::free_balance(&0), 95); // ProposalBond: Permill::from_percent(5); + assert_eq!(Ring::free_balance(&1), 98); // No changes + assert_eq!(Kton::free_balance(&1), 98); // No changes + assert_eq!(Ring::free_balance(&2), 1); // No changes + assert_eq!(Kton::free_balance(&2), 1); // No changes + assert_eq!(Ring::free_balance(&3), 0); // No changes + assert_eq!(Kton::free_balance(&3), 0); // No changes + assert_eq!(Treasury::pot::(), 50); // Burn: Permill::from_percent(50); **The Burned Balances just burned?** + assert_eq!(Treasury::pot::(), 50); // Burn: Permill::from_percent(50); **The Burned Balances just burned?** + + // @3: Check balances on the perid after spend perid + >::on_finalize(3); + assert_eq!(Ring::free_balance(&0), 95); // No changes from last perid + assert_eq!(Kton::free_balance(&0), 95); // No changes from last perid + assert_eq!(Ring::free_balance(&1), 98); // No changes + assert_eq!(Kton::free_balance(&1), 98); // No changes + assert_eq!(Ring::free_balance(&2), 1); // No changes + assert_eq!(Kton::free_balance(&2), 1); // No changes + assert_eq!(Ring::free_balance(&3), 0); // No changes + assert_eq!(Kton::free_balance(&3), 0); // No changes + assert_eq!(Treasury::pot::(), 50); // No changes from last perid + assert_eq!(Treasury::pot::(), 50); // No changes from last perid + + // @4: The second spend perid + >::on_finalize(4); + assert_eq!(Ring::free_balance(&0), 95); // No changes from last perid + assert_eq!(Kton::free_balance(&0), 95); // No changes from last perid + assert_eq!(Ring::free_balance(&1), 98); // No changes + assert_eq!(Kton::free_balance(&1), 98); // No changes + assert_eq!(Ring::free_balance(&2), 1); // No changes + assert_eq!(Kton::free_balance(&2), 1); // No changes + assert_eq!(Ring::free_balance(&3), 0); // No changes + assert_eq!(Kton::free_balance(&3), 0); // No changes + assert_eq!(Treasury::pot::(), 25); // No changes from last perid + assert_eq!(Treasury::pot::(), 25); // No changes from last perid + }); +}