diff --git a/Cargo.lock b/Cargo.lock index 9463f4b66c15..1315dc32b6d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2268,6 +2268,7 @@ dependencies = [ "sr-std 2.0.0", "sr-version 2.0.0", "srml-aura 2.0.0", + "srml-authorship 0.1.0", "srml-balances 2.0.0", "srml-contracts 2.0.0", "srml-council 2.0.0", @@ -3772,6 +3773,7 @@ dependencies = [ "srml-support-procedural 2.0.0", "srml-system 2.0.0", "substrate-inherents 2.0.0", + "substrate-primitives 2.0.0", ] [[package]] diff --git a/core/sr-primitives/src/lib.rs b/core/sr-primitives/src/lib.rs index 3874a87d2b68..28c954adb23b 100644 --- a/core/sr-primitives/src/lib.rs +++ b/core/sr-primitives/src/lib.rs @@ -69,6 +69,14 @@ pub type Justification = Vec; use traits::{Verify, Lazy}; +/// A module identifier. These are per module and should be stored in a registry somewhere. +#[derive(Clone, Copy, Eq, PartialEq, Encode, Decode)] +pub struct ModuleId(pub [u8; 8]); + +impl traits::TypeId for ModuleId { + const TYPE_ID: [u8; 4] = *b"modl"; +} + /// A String that is a `&'static str` on `no_std` and a `Cow<'static, str>` on `std`. #[cfg(feature = "std")] pub type RuntimeString = ::std::borrow::Cow<'static, str>; diff --git a/core/sr-primitives/src/traits.rs b/core/sr-primitives/src/traits.rs index b2bb7ab80511..5e54b6117a47 100644 --- a/core/sr-primitives/src/traits.rs +++ b/core/sr-primitives/src/traits.rs @@ -830,6 +830,110 @@ pub trait OpaqueKeys: Clone { fn ownership_proof_is_valid(&self, _proof: &[u8]) -> bool { true } } +struct TrailingZeroInput<'a>(&'a [u8]); +impl<'a> codec::Input for TrailingZeroInput<'a> { + fn read(&mut self, into: &mut [u8]) -> usize { + let len = into.len().min(self.0.len()); + into[..len].copy_from_slice(&self.0[..len]); + for i in &mut into[len..] { + *i = 0; + } + self.0 = &self.0[len..]; + into.len() + } +} + +/// This type can be converted into and possibly from an AccountId (which itself is generic). +pub trait AccountIdConversion: Sized { + /// Convert into an account ID. This is infallible. + fn into_account(&self) -> AccountId; + + /// Try to convert an account ID into this type. Might not succeed. + fn try_from_account(a: &AccountId) -> Option; +} + +/// Provide a simply 4 byte identifier for a type. +pub trait TypeId { + /// Simple 4 byte identifier. + const TYPE_ID: [u8; 4]; +} + +/// Format is TYPE_ID ++ encode(parachain ID) ++ 00.... where 00... is indefinite trailing zeroes to fill AccountId. +impl AccountIdConversion for Id { + fn into_account(&self) -> T { + (Id::TYPE_ID, self).using_encoded(|b| + T::decode(&mut TrailingZeroInput(b)) + ).unwrap_or_default() + } + + fn try_from_account(x: &T) -> Option { + x.using_encoded(|d| { + if &d[0..4] != Id::TYPE_ID { return None } + let mut cursor = &d[4..]; + let result = Decode::decode(&mut cursor)?; + if cursor.iter().all(|x| *x == 0) { + Some(result) + } else { + None + } + }) + } +} + +#[cfg(test)] +mod tests { + use super::AccountIdConversion; + use crate::codec::{Encode, Decode}; + + #[derive(Encode, Decode, Default, PartialEq, Debug)] + struct U32Value(u32); + impl super::TypeId for U32Value { + const TYPE_ID: [u8; 4] = [0x0d, 0xf0, 0xfe, 0xca]; + } + // cafef00d + + #[derive(Encode, Decode, Default, PartialEq, Debug)] + struct U16Value(u16); + impl super::TypeId for U16Value { + const TYPE_ID: [u8; 4] = [0xfe, 0xca, 0x0d, 0xf0]; + } + // f00dcafe + + type AccountId = u64; + + #[test] + fn into_account_should_work() { + let r: AccountId = U32Value::into_account(&U32Value(0xdeadbeef)); + assert_eq!(r, 0x_deadbeef_cafef00d); + } + + #[test] + fn try_from_account_should_work() { + let r = U32Value::try_from_account(&0x_deadbeef_cafef00d_u64); + assert_eq!(r.unwrap(), U32Value(0xdeadbeef)); + } + + #[test] + fn into_account_with_fill_should_work() { + let r: AccountId = U16Value::into_account(&U16Value(0xc0da)); + assert_eq!(r, 0x_0000_c0da_f00dcafe); + } + + #[test] + fn try_from_account_with_fill_should_work() { + let r = U16Value::try_from_account(&0x0000_c0da_f00dcafe_u64); + assert_eq!(r.unwrap(), U16Value(0xc0da)); + } + + #[test] + fn bad_try_from_account_should_fail() { + let r = U16Value::try_from_account(&0x0000_c0de_baadcafe_u64); + assert!(r.is_none()); + let r = U16Value::try_from_account(&0x0100_c0da_f00dcafe_u64); + assert!(r.is_none()); + } +} + /// Calls a given macro a number of times with a set of fixed params and an incrementing numeral. /// e.g. /// ```nocompile diff --git a/core/test-runtime/wasm/Cargo.lock b/core/test-runtime/wasm/Cargo.lock index 9016a4cfa2ff..046bce56de0b 100644 --- a/core/test-runtime/wasm/Cargo.lock +++ b/core/test-runtime/wasm/Cargo.lock @@ -2397,6 +2397,7 @@ dependencies = [ "srml-metadata 2.0.0", "srml-support-procedural 2.0.0", "substrate-inherents 2.0.0", + "substrate-primitives 2.0.0", ] [[package]] diff --git a/node-template/runtime/wasm/Cargo.lock b/node-template/runtime/wasm/Cargo.lock index cbcc5dc60497..3b5f799caf7b 100644 --- a/node-template/runtime/wasm/Cargo.lock +++ b/node-template/runtime/wasm/Cargo.lock @@ -2521,6 +2521,7 @@ dependencies = [ "srml-metadata 2.0.0", "srml-support-procedural 2.0.0", "substrate-inherents 2.0.0", + "substrate-primitives 2.0.0", ] [[package]] diff --git a/node/executor/src/lib.rs b/node/executor/src/lib.rs index d266265b1e82..edf99e064e59 100644 --- a/node/executor/src/lib.rs +++ b/node/executor/src/lib.rs @@ -54,7 +54,7 @@ mod tests { use system::{EventRecord, Phase}; use node_runtime::{Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Call, Runtime, Balances, BuildStorage, GenesisConfig, BalancesConfig, SessionConfig, StakingConfig, System, - SystemConfig, GrandpaConfig, IndicesConfig, Event, SessionKeys}; + SystemConfig, GrandpaConfig, IndicesConfig, Event, SessionKeys, Treasury}; use wabt; use primitives::map; @@ -566,6 +566,34 @@ mod tests { event: Event::system(system::Event::ExtrinsicSuccess), topics: vec![], }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: Event::indices( + indices::RawEvent::NewAccountIndex(Treasury::account_id(), 6) + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: Event::balances( + balances::RawEvent::NewAccount(Treasury::account_id(), 0) + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: Event::indices( + indices::RawEvent::NewAccountIndex(Default::default(), 7) + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: Event::balances( + balances::RawEvent::NewAccount(Default::default(), 1) + ), + topics: vec![], + }, EventRecord { phase: Phase::ApplyExtrinsic(1), event: Event::balances(balances::RawEvent::Transfer( diff --git a/node/runtime/Cargo.toml b/node/runtime/Cargo.toml index 547867cda9ac..755f89016ae9 100644 --- a/node/runtime/Cargo.toml +++ b/node/runtime/Cargo.toml @@ -16,6 +16,7 @@ offchain-primitives = { package = "substrate-offchain-primitives", path = "../.. version = { package = "sr-version", path = "../../core/sr-version", default-features = false } support = { package = "srml-support", path = "../../srml/support", default-features = false } aura = { package = "srml-aura", path = "../../srml/aura", default-features = false } +authorship = { package = "srml-authorship", path = "../../srml/authorship", default-features = false } balances = { package = "srml-balances", path = "../../srml/balances", default-features = false } contracts = { package = "srml-contracts", path = "../../srml/contracts", default-features = false } council = { package = "srml-council", path = "../../srml/council", default-features = false } @@ -48,6 +49,7 @@ std = [ "runtime_primitives/std", "support/std", "aura/std", + "authorship/std", "balances/std", "contracts/std", "council/std", diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index ca48837ce2ae..f0a5a0acf341 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -21,7 +21,9 @@ #![recursion_limit="256"] use rstd::prelude::*; -use support::{construct_runtime, parameter_types}; +use support::{ + construct_runtime, parameter_types, traits::{SplitTwoWays, Currency, OnUnbalanced} +}; use substrate_primitives::u32_trait::{_1, _2, _3, _4}; use node_primitives::{ AccountId, AccountIndex, Balance, BlockNumber, Hash, Index, Signature, AuraId @@ -58,8 +60,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("node"), impl_name: create_runtime_str!("substrate-node"), authoring_version: 10, - spec_version: 99, - impl_version: 106, + spec_version: 100, + impl_version: 100, apis: RUNTIME_API_VERSIONS, }; @@ -72,6 +74,23 @@ pub fn native_version() -> NativeVersion { } } +type NegativeImbalance = >::NegativeImbalance; + +pub struct Author; + +impl OnUnbalanced for Author { + fn on_unbalanced(amount: NegativeImbalance) { + Balances::resolve_creating(&Authorship::author(), amount); + } +} + +pub type DealWithFees = SplitTwoWays< + Balance, + NegativeImbalance, + _4, Treasury, // 4 parts (80%) goes to the treasury. + _1, Author, // 1 part (20%) goes to the block author. +>; + pub struct CurrencyToVoteHandler; impl CurrencyToVoteHandler { @@ -115,7 +134,7 @@ impl balances::Trait for Runtime { type OnFreeBalanceZero = ((Staking, Contracts), Session); type OnNewAccount = Indices; type Event = Event; - type TransactionPayment = (); + type TransactionPayment = DealWithFees; type DustRemoval = (); type TransferPayment = (); } @@ -125,6 +144,18 @@ impl timestamp::Trait for Runtime { type OnTimestampSet = Aura; } +parameter_types! { + pub const UncleGenerations: u64 = 0; +} + +// TODO: #2986 implement this properly +impl authorship::Trait for Runtime { + type FindAuthor = (); + type UncleGenerations = UncleGenerations; + type FilterUncle = (); + type EventHandler = (); +} + parameter_types! { pub const Period: BlockNumber = 10 * MINUTES; pub const Offset: BlockNumber = 0; @@ -250,6 +281,7 @@ construct_runtime!( System: system::{Module, Call, Storage, Config, Event}, Aura: aura::{Module, Config, Inherent(Timestamp)}, Timestamp: timestamp::{Module, Call, Storage, Config, Inherent}, + Authorship: authorship::{Module, Call, Storage}, Indices: indices, Balances: balances, Session: session::{Module, Call, Storage, Event, Config}, diff --git a/node/runtime/wasm/Cargo.lock b/node/runtime/wasm/Cargo.lock index 73e383328593..3dc6ac51c8c7 100644 --- a/node/runtime/wasm/Cargo.lock +++ b/node/runtime/wasm/Cargo.lock @@ -1499,6 +1499,7 @@ dependencies = [ "sr-std 2.0.0", "sr-version 2.0.0", "srml-aura 2.0.0", + "srml-authorship 0.1.0", "srml-balances 2.0.0", "srml-contracts 2.0.0", "srml-council 2.0.0", @@ -2452,6 +2453,19 @@ dependencies = [ "substrate-primitives 2.0.0", ] +[[package]] +name = "srml-authorship" +version = "0.1.0" +dependencies = [ + "parity-codec 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "sr-io 2.0.0", + "sr-primitives 2.0.0", + "sr-std 2.0.0", + "srml-support 2.0.0", + "srml-system 2.0.0", + "substrate-primitives 2.0.0", +] + [[package]] name = "srml-balances" version = "2.0.0" @@ -2642,6 +2656,7 @@ dependencies = [ "srml-metadata 2.0.0", "srml-support-procedural 2.0.0", "substrate-inherents 2.0.0", + "substrate-primitives 2.0.0", ] [[package]] diff --git a/srml/authorship/src/lib.rs b/srml/authorship/src/lib.rs index de14efea69f0..127ff88dbb3b 100644 --- a/srml/authorship/src/lib.rs +++ b/srml/authorship/src/lib.rs @@ -18,6 +18,8 @@ //! //! This tracks the current author of the block and recent uncles. +#![cfg_attr(not(feature = "std"), no_std)] + use rstd::prelude::*; use rstd::collections::btree_set::BTreeSet; use srml_support::{decl_module, decl_storage, for_each_tuple, StorageValue}; @@ -102,6 +104,15 @@ pub trait FilterUncle { -> Result<(Option, Self::Accumulator), &'static str>; } +impl FilterUncle for () { + type Accumulator = (); + fn filter_uncle(_: &H, acc: Self::Accumulator) + -> Result<(Option, Self::Accumulator), &'static str> + { + Ok((None, acc)) + } +} + /// A filter on uncles which verifies seals and does no additional checks. /// This is well-suited to consensus modes such as PoW where the cost of /// equivocating is high. @@ -201,8 +212,8 @@ decl_module! { fn on_finalize() { // ensure we never go to trie with these values. - let _ = ::Author::take(); - let _ = ::DidSetUncles::take(); + ::Author::kill(); + ::DidSetUncles::kill(); } /// Provide a set of uncles. diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index c7e7fed3716d..94093a587b9c 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -161,7 +161,7 @@ use srml_support::traits::{ use srml_support::dispatch::Result; use primitives::traits::{ Zero, SimpleArithmetic, StaticLookup, Member, CheckedAdd, CheckedSub, - MaybeSerializeDebug, Saturating + MaybeSerializeDebug, Saturating, Bounded }; use system::{IsDeadAccount, OnNewAccount, ensure_signed}; @@ -313,7 +313,9 @@ decl_storage! { /// /// `system::AccountNonce` is also deleted if `ReservedBalance` is also zero (it also gets /// collapsed to zero if it ever becomes less than `ExistentialDeposit`. - pub FreeBalance get(free_balance) build(|config: &GenesisConfig| config.balances.clone()): map T::AccountId => T::Balance; + pub FreeBalance get(free_balance) + build(|config: &GenesisConfig| config.balances.clone()): + map T::AccountId => T::Balance; /// The amount of the balance of a given account that is externally reserved; this can still get /// slashed, but gets slashed last of all. @@ -735,6 +737,26 @@ where >::get(who) } + fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { + >::mutate(|issued| + issued.checked_sub(&amount).unwrap_or_else(|| { + amount = *issued; + Zero::zero() + }) + ); + PositiveImbalance::new(amount) + } + + fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance { + >::mutate(|issued| + *issued = issued.checked_add(&amount).unwrap_or_else(|| { + amount = Self::Balance::max_value() - *issued; + Self::Balance::max_value() + }) + ); + NegativeImbalance::new(amount) + } + // # // Despite iterating over a list of locks, they are limited by the number of // lock IDs, which means the number of runtime modules that intend to use and create locks. diff --git a/srml/support/Cargo.toml b/srml/support/Cargo.toml index ba471ac46359..88cad9651a81 100644 --- a/srml/support/Cargo.toml +++ b/srml/support/Cargo.toml @@ -11,6 +11,7 @@ srml-metadata = { path = "../metadata", default-features = false } sr-std = { path = "../../core/sr-std", default-features = false } runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false } sr-primitives = { path = "../../core/sr-primitives", default-features = false } +substrate-primitives = { path = "../../core/primitives", default-features = false } inherents = { package = "substrate-inherents", path = "../../core/inherents", default-features = false } srml-support-procedural = { path = "./procedural" } paste = "0.1" diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 2a2af334d733..7dc10a668408 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -18,10 +18,11 @@ //! //! NOTE: If you're looking for `parameter_types`, it has moved in to the top-level module. -use crate::rstd::result; +use crate::rstd::{result, marker::PhantomData, ops::Div}; use crate::codec::{Codec, Encode, Decode}; +use substrate_primitives::u32_trait::Value as U32; use crate::runtime_primitives::traits::{ - MaybeSerializeDebug, SimpleArithmetic + MaybeSerializeDebug, SimpleArithmetic, Saturating }; use crate::runtime_primitives::ConsensusEngineId; @@ -111,6 +112,14 @@ pub trait FindAuthor { where I: 'a + IntoIterator; } +impl FindAuthor for () { + fn find_author<'a, I>(_: I) -> Option + where I: 'a + IntoIterator + { + None + } +} + /// A trait for verifying the seal of a header and returning the author. pub trait VerifySeal { /// Verify a header and return the author, if any. @@ -270,6 +279,34 @@ impl< } } +/// Split an unbalanced amount two ways between a common divisor. +pub struct SplitTwoWays< + Balance, + Imbalance, + Part1, + Target1, + Part2, + Target2, +>(PhantomData<(Balance, Imbalance, Part1, Target1, Part2, Target2)>); + +impl< + Balance: From + Saturating + Div, + I: Imbalance, + Part1: U32, + Target1: OnUnbalanced, + Part2: U32, + Target2: OnUnbalanced, +> OnUnbalanced for SplitTwoWays +{ + fn on_unbalanced(amount: I) { + let total: u32 = Part1::VALUE + Part2::VALUE; + let amount1 = amount.peek().saturating_mul(Part1::VALUE.into()) / total.into(); + let (imb1, imb2) = amount.split(amount1); + Target1::on_unbalanced(imb1); + Target2::on_unbalanced(imb2); + } +} + /// Abstraction over a fungible assets system. pub trait Currency { /// The balance of an account. @@ -299,6 +336,21 @@ pub trait Currency { /// `ExistentialDeposit`. fn minimum_balance() -> Self::Balance; + /// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will + /// typically be used to reduce an account by the same amount with e.g. `settle`. + /// + /// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example + /// in the case of underflow. + fn burn(amount: Self::Balance) -> Self::PositiveImbalance; + + /// Increase the total issuance by `amount` and return the according imbalance. The imbalance + /// will typically be used to increase an account by the same amount with e.g. + /// `resolve_into_existing` or `resolve_creating`. + /// + /// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example + /// in the case of overflow. + fn issue(amount: Self::Balance) -> Self::NegativeImbalance; + /// The 'free' balance of a given account. /// /// This is the only balance that matters in terms of most operations on tokens. It alone @@ -355,17 +407,18 @@ pub trait Currency { value: Self::Balance ) -> result::Result; - /// Removes some free balance from `who` account for `reason` if possible. If `liveness` is `KeepAlive`, - /// then no less than `ExistentialDeposit` must be left remaining. - /// - /// This checks any locks, vesting, and liquidity requirements. If the removal is not possible, then it - /// returns `Err`. - fn withdraw( + /// Similar to deposit_creating, only accepts a `NegativeImbalance` and returns nothing on + /// success. + fn resolve_into_existing( who: &AccountId, - value: Self::Balance, - reason: WithdrawReason, - liveness: ExistenceRequirement, - ) -> result::Result; + value: Self::NegativeImbalance, + ) -> result::Result<(), Self::NegativeImbalance> { + let v = value.peek(); + match Self::deposit_into_existing(who, v) { + Ok(opposite) => Ok(drop(value.offset(opposite))), + _ => Err(value), + } + } /// Adds up to `value` to the free balance of `who`. If `who` doesn't exist, it is created. /// @@ -375,6 +428,45 @@ pub trait Currency { value: Self::Balance, ) -> Self::PositiveImbalance; + /// Similar to deposit_creating, only accepts a `NegativeImbalance` and returns nothing on + /// success. + fn resolve_creating( + who: &AccountId, + value: Self::NegativeImbalance, + ) { + let v = value.peek(); + drop(value.offset(Self::deposit_creating(who, v))); + } + + /// Removes some free balance from `who` account for `reason` if possible. If `liveness` is + /// `KeepAlive`, then no less than `ExistentialDeposit` must be left remaining. + /// + /// This checks any locks, vesting, and liquidity requirements. If the removal is not possible, + /// then it returns `Err`. + /// + /// If the operation is successful, this will return `Ok` with a `NegativeImbalance` whose value + /// is `value`. + fn withdraw( + who: &AccountId, + value: Self::Balance, + reason: WithdrawReason, + liveness: ExistenceRequirement, + ) -> result::Result; + + /// Similar to withdraw, only accepts a `PositiveImbalance` and returns nothing on success. + fn settle( + who: &AccountId, + value: Self::PositiveImbalance, + reason: WithdrawReason, + liveness: ExistenceRequirement, + ) -> result::Result<(), Self::PositiveImbalance> { + let v = value.peek(); + match Self::withdraw(who, v, reason, liveness) { + Ok(opposite) => Ok(drop(value.offset(opposite))), + _ => Err(value), + } + } + /// Ensure an account's free balance equals some value; this will create the account /// if needed. /// diff --git a/srml/treasury/src/lib.rs b/srml/treasury/src/lib.rs index e46387ea89d0..f8c844c81032 100644 --- a/srml/treasury/src/lib.rs +++ b/srml/treasury/src/lib.rs @@ -70,11 +70,14 @@ #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; use rstd::prelude::*; -use srml_support::{StorageValue, StorageMap, decl_module, decl_storage, decl_event, ensure}; -use srml_support::traits::{Currency, ReservableCurrency, OnDilution, OnUnbalanced, Imbalance}; -use runtime_primitives::{Permill, - traits::{Zero, EnsureOrigin, StaticLookup, Saturating, CheckedSub, CheckedMul} +use srml_support::{StorageValue, StorageMap, decl_module, decl_storage, decl_event, ensure, print}; +use srml_support::traits::{ + Currency, ReservableCurrency, OnDilution, OnUnbalanced, Imbalance, WithdrawReason, + ExistenceRequirement }; +use runtime_primitives::{Permill, ModuleId, traits::{ + Zero, EnsureOrigin, StaticLookup, CheckedSub, CheckedMul, AccountIdConversion +}}; use parity_codec::{Encode, Decode}; use system::ensure_signed; @@ -82,6 +85,8 @@ type BalanceOf = <::Currency as Currency<::Ac type PositiveImbalanceOf = <::Currency as Currency<::AccountId>>::PositiveImbalance; type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; +const MODULE_ID: ModuleId = ModuleId(*b"py/trsry"); + pub trait Trait: system::Trait { /// The staking balance. type Currency: Currency + ReservableCurrency; @@ -135,12 +140,6 @@ decl_module! { Self::deposit_event(RawEvent::Proposed(c)); } - /// Set the balance of funds available to spend. - fn set_pot(#[compact] new_pot: BalanceOf) { - // Put the new value into storage. - >::put(new_pot); - } - /// (Re-)configure this module. fn configure( #[compact] proposal_bond: Permill, @@ -224,9 +223,6 @@ decl_storage! { // State... - /// Total funds available to this module for spending. - Pot get(pot): BalanceOf; - /// Number of proposals that have been made. ProposalCount get(proposal_count): ProposalIndex; @@ -260,6 +256,14 @@ decl_event!( impl Module { // Add public immutables and private mutables. + /// The account ID of the treasury pot. + /// + /// This actually does computation. If you need to keep using it, then make sure you cache the + /// value and only call this once. + pub fn account_id() -> T::AccountId { + MODULE_ID.into_account() + } + /// The needed bond for a proposal whose spend is `value`. fn calculate_bond(value: BalanceOf) -> BalanceOf { Self::proposal_bond_minimum().max(Self::proposal_bond() * value) @@ -298,18 +302,36 @@ impl Module { }); }); - T::MintedForSpending::on_unbalanced(imbalance); - if !missed_any { // burn some proportion of the remaining budget if we run a surplus. let burn = (Self::burn() * budget_remaining).min(budget_remaining); budget_remaining -= burn; + imbalance.subsume(T::Currency::burn(burn)); Self::deposit_event(RawEvent::Burnt(burn)) } + if let Err(problem) = T::Currency::settle( + &Self::account_id(), + imbalance, + WithdrawReason::Transfer, + 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)); + } - >::put(budget_remaining); + fn pot() -> BalanceOf { + T::Currency::free_balance(&Self::account_id()) + } +} + +impl OnUnbalanced> for Module { + fn on_unbalanced(amount: NegativeImbalanceOf) { + T::Currency::resolve_creating(&Self::account_id(), amount); } } @@ -322,7 +344,7 @@ impl OnDilution> for Module { if let Some(funding) = total_issuance.checked_sub(&portion) { let funding = funding / portion; if let Some(funding) = funding.checked_mul(&minted) { - >::mutate(|x| *x = x.saturating_add(funding)); + Self::on_unbalanced(T::Currency::issue(funding)); } } } @@ -519,6 +541,7 @@ mod tests { fn accepted_spend_proposal_enacted_on_spend_period() { with_externalities(&mut new_test_ext(), || { Treasury::on_dilution(100, 100); + assert_eq!(Treasury::pot(), 100); assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); assert_ok!(Treasury::approve_proposal(Origin::ROOT, 0)); @@ -536,25 +559,27 @@ mod tests { fn on_dilution_quantization_effects() { with_externalities(&mut new_test_ext(), || { // minted = 1% of total issuance for all cases - let _ = Treasury::set_pot(0); assert_eq!(Balances::total_issuance(), 200); Treasury::on_dilution(2, 66); // portion = 33% of total issuance assert_eq!(Treasury::pot(), 4); // should increase by 4 (200 - 66) / 66 * 2 + Balances::make_free_balance_be(&Treasury::account_id(), 0); Treasury::on_dilution(2, 67); // portion = 33+eps% of total issuance - assert_eq!(Treasury::pot(), 6); // should increase by 2 (200 - 67) / 67 * 2 + assert_eq!(Treasury::pot(), 2); // should increase by 2 (200 - 67) / 67 * 2 + Balances::make_free_balance_be(&Treasury::account_id(), 0); Treasury::on_dilution(2, 100); // portion = 50% of total issuance - assert_eq!(Treasury::pot(), 8); // should increase by 2 (200 - 100) / 100 * 2 + assert_eq!(Treasury::pot(), 2); // should increase by 2 (200 - 100) / 100 * 2 + Balances::make_free_balance_be(&Treasury::account_id(), 0); // If any more than 50% of the network is staked (i.e. (2 * portion) > total_issuance) // then the pot will not increase. Treasury::on_dilution(2, 101); // portion = 50+eps% of total issuance - assert_eq!(Treasury::pot(), 8); // should increase by 0 (200 - 101) / 101 * 2 + assert_eq!(Treasury::pot(), 0); // should increase by 0 (200 - 101) / 101 * 2 Treasury::on_dilution(2, 134); // portion = 67% of total issuance - assert_eq!(Treasury::pot(), 8); // should increase by 0 (200 - 134) / 134 * 2 + assert_eq!(Treasury::pot(), 0); // should increase by 0 (200 - 134) / 134 * 2 }); } @@ -572,7 +597,7 @@ mod tests { Treasury::on_dilution(100, 100); >::on_finalize(4); assert_eq!(Balances::free_balance(&3), 150); - assert_eq!(Treasury::pot(), 25); + assert_eq!(Treasury::pot(), 75); }); } }