// This file is part of Substrate. // Copyright (C) 2017-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. //! # 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::Config`](./trait.Config.html) //! - [`Call`](./enum.Call.html) //! //! ## 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 //! //! General spending/proposal protocol: //! - `propose_spend` - Make a spending proposal and stake the required deposit. //! - `reject_proposal` - Reject a proposal, slashing the deposit. //! - `approve_proposal` - Accept the proposal, returning the deposit. //! //! ## GenesisConfig //! //! The Treasury module depends on the [`GenesisConfig`](./struct.GenesisConfig.html). #![cfg_attr(not(feature = "std"), no_std)] #[cfg(test)] mod tests; mod benchmarking; pub mod weights; #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; use sp_std::prelude::*; use frame_support::{decl_module, decl_storage, decl_event, ensure, print, decl_error}; use frame_support::traits::{ Currency, Get, Imbalance, OnUnbalanced, ExistenceRequirement::{KeepAlive}, ReservableCurrency, WithdrawReasons }; use sp_runtime::{Permill, ModuleId, RuntimeDebug, traits::{ Zero, StaticLookup, AccountIdConversion, Saturating }}; use frame_support::weights::{Weight, DispatchClass}; use frame_support::traits::{EnsureOrigin}; use codec::{Encode, Decode}; use frame_system::{ensure_signed}; pub use weights::WeightInfo; pub type BalanceOf = <>::Currency as Currency<::AccountId>>::Balance; pub type PositiveImbalanceOf = <>::Currency as Currency<::AccountId>>::PositiveImbalance; pub type NegativeImbalanceOf = <>::Currency as Currency<::AccountId>>::NegativeImbalance; pub trait Config: frame_system::Config { /// The treasury's module id, used for deriving its sovereign account ID. type ModuleId: Get; /// The staking balance. type Currency: Currency + ReservableCurrency; /// Origin from which approvals must come. type ApproveOrigin: EnsureOrigin; /// Origin from which rejections must come. type RejectOrigin: EnsureOrigin; /// The overarching event type. type Event: From> + Into<::Event>; /// Handler for the unbalanced decrease when slashing for a rejected proposal or bounty. type OnSlash: 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>; /// Period between successive spends. type SpendPeriod: Get; /// Percentage of spare funds (if any) that are burnt per spend period. type Burn: Get; /// Handler for the unbalanced decrease when treasury funds are burned. type BurnDestination: OnUnbalanced>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; /// Runtime hooks to external pallet using treasury to compute spend funds. type SpendFunds: SpendFunds; } /// A trait to allow the Treasury Pallet to spend it's funds for other purposes. /// There is an expectation that the implementer of this trait will correctly manage /// the mutable variables passed to it: /// * `budget_remaining`: How much available funds that can be spent by the treasury. /// As funds are spent, you must correctly deduct from this value. /// * `imbalance`: Any imbalances that you create should be subsumed in here to /// maximize efficiency of updating the total issuance. (i.e. `deposit_creating`) /// * `total_weight`: Track any weight that your `spend_fund` implementation uses by /// updating this value. /// * `missed_any`: If there were items that you want to spend on, but there were /// not enough funds, mark this value as `true`. This will prevent the treasury /// from burning the excess funds. #[impl_trait_for_tuples::impl_for_tuples(30)] pub trait SpendFunds, I=DefaultInstance> { fn spend_funds( budget_remaining: &mut BalanceOf, imbalance: &mut PositiveImbalanceOf, total_weight: &mut Weight, missed_any: &mut bool, ); } /// An index of a proposal. Just a `u32`. pub type ProposalIndex = u32; /// A spending proposal. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] pub struct Proposal { /// The account proposing it. proposer: AccountId, /// The (total) amount that should be paid if the proposal is accepted. value: Balance, /// The account to whom the payment should be made if the proposal is accepted. beneficiary: AccountId, /// The amount held on deposit (reserved) for making this proposal. bond: Balance, } decl_storage! { trait Store for Module, I: Instance=DefaultInstance> as Treasury { /// Number of proposals that have been made. ProposalCount get(fn proposal_count): ProposalIndex; /// Proposals that have been made. pub Proposals get(fn proposals): map hasher(twox_64_concat) ProposalIndex => Option>>; /// Proposal indices that have been approved but not yet awarded. pub Approvals get(fn approvals): Vec; } add_extra_genesis { build(|_config| { // Create Treasury account let account_id = >::account_id(); let min = T::Currency::minimum_balance(); if T::Currency::free_balance(&account_id) < min { let _ = T::Currency::make_free_balance_be( &account_id, min, ); } }); } } decl_event!( pub enum Event where Balance = BalanceOf, ::AccountId, { /// New proposal. \[proposal_index\] Proposed(ProposalIndex), /// We have ended a spend period and will now allocate funds. \[budget_remaining\] Spending(Balance), /// Some funds have been allocated. \[proposal_index, award, beneficiary\] Awarded(ProposalIndex, Balance, AccountId), /// A proposal was rejected; funds were slashed. \[proposal_index, slashed\] Rejected(ProposalIndex, Balance), /// Some of our funds have been burnt. \[burn\] Burnt(Balance), /// Spending has finished; this is the amount that rolls over until next spend. /// \[budget_remaining\] Rollover(Balance), /// Some funds have been deposited. \[deposit\] Deposit(Balance), } ); decl_error! { /// Error for the treasury module. pub enum Error for Module, I: Instance> { /// Proposer's balance is too low. InsufficientProposersBalance, /// No proposal or bounty at that index. InvalidIndex, } } decl_module! { pub struct Module, I: Instance=DefaultInstance> for enum Call where origin: T::Origin { /// 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. const ProposalBond: Permill = T::ProposalBond::get(); /// Minimum amount of funds that should be placed in a deposit for making a proposal. const ProposalBondMinimum: BalanceOf = T::ProposalBondMinimum::get(); /// Period between successive spends. const SpendPeriod: T::BlockNumber = T::SpendPeriod::get(); /// Percentage of spare funds (if any) that are burnt per spend period. const Burn: Permill = T::Burn::get(); /// The treasury's module id, used for deriving its sovereign account ID. const ModuleId: ModuleId = T::ModuleId::get(); type Error = Error; fn deposit_event() = default; /// Put forward a suggestion for spending. A deposit proportional to the value /// is reserved and slashed if the proposal is rejected. It is returned once the /// proposal is awarded. /// /// # /// - Complexity: O(1) /// - DbReads: `ProposalCount`, `origin account` /// - DbWrites: `ProposalCount`, `Proposals`, `origin account` /// # #[weight = T::WeightInfo::propose_spend()] pub fn propose_spend( origin, #[compact] value: BalanceOf, beneficiary: ::Source ) { let proposer = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; let bond = Self::calculate_bond(value); T::Currency::reserve(&proposer, bond) .map_err(|_| Error::::InsufficientProposersBalance)?; let c = Self::proposal_count(); >::put(c + 1); >::insert(c, Proposal { proposer, value, beneficiary, bond }); Self::deposit_event(RawEvent::Proposed(c)); } /// Reject a proposed spend. The original deposit will be slashed. /// /// May only be called from `T::RejectOrigin`. /// /// # /// - Complexity: O(1) /// - DbReads: `Proposals`, `rejected proposer account` /// - DbWrites: `Proposals`, `rejected proposer account` /// # #[weight = (T::WeightInfo::reject_proposal(), DispatchClass::Operational)] pub fn reject_proposal(origin, #[compact] proposal_id: ProposalIndex) { T::RejectOrigin::ensure_origin(origin)?; let proposal = >::take(&proposal_id).ok_or(Error::::InvalidIndex)?; let value = proposal.bond; let imbalance = T::Currency::slash_reserved(&proposal.proposer, value).0; T::OnSlash::on_unbalanced(imbalance); Self::deposit_event(Event::::Rejected(proposal_id, value)); } /// Approve a proposal. At a later time, the proposal will be allocated to the beneficiary /// and the original deposit will be returned. /// /// May only be called from `T::ApproveOrigin`. /// /// # /// - Complexity: O(1). /// - DbReads: `Proposals`, `Approvals` /// - DbWrite: `Approvals` /// # #[weight = (T::WeightInfo::approve_proposal(), DispatchClass::Operational)] pub fn approve_proposal(origin, #[compact] proposal_id: ProposalIndex) { T::ApproveOrigin::ensure_origin(origin)?; ensure!(>::contains_key(proposal_id), Error::::InvalidIndex); Approvals::::append(proposal_id); } /// # /// - Complexity: `O(A)` where `A` is the number of approvals /// - Db reads and writes: `Approvals`, `pot account data` /// - Db reads and writes per approval: /// `Proposals`, `proposer account data`, `beneficiary account data` /// - The weight is overestimated if some approvals got missed. /// # fn on_initialize(n: T::BlockNumber) -> Weight { // Check to see if we should spend some funds! if (n % T::SpendPeriod::get()).is_zero() { Self::spend_funds() } else { 0 } } } } impl, I: Instance> 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 { T::ModuleId::get().into_account() } /// The needed bond for a proposal whose spend is `value`. fn calculate_bond(value: BalanceOf) -> BalanceOf { T::ProposalBondMinimum::get().max(T::ProposalBond::get() * value) } /// Spend some money! returns number of approvals before spend. pub fn spend_funds() -> Weight { let mut total_weight: Weight = Zero::zero(); let mut budget_remaining = Self::pot(); Self::deposit_event(RawEvent::Spending(budget_remaining)); let account_id = Self::account_id(); let mut missed_any = false; let mut imbalance = >::zero(); let proposals_len = Approvals::::mutate(|v| { let proposals_approvals_len = v.len() as u32; 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::Currency::unreserve(&p.proposer, p.bond); // provide the allocation. imbalance.subsume(T::Currency::deposit_creating(&p.beneficiary, p.value)); Self::deposit_event(RawEvent::Awarded(index, p.value, p.beneficiary)); false } else { missed_any = true; true } } else { false } }); proposals_approvals_len }); total_weight += T::WeightInfo::on_initialize_proposals(proposals_len); // Call Runtime hooks to external pallet using treasury to compute spend funds. T::SpendFunds::spend_funds( &mut budget_remaining, &mut imbalance, &mut total_weight, &mut missed_any); if !missed_any { // 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; let (debit, credit) = T::Currency::pair(burn); imbalance.subsume(debit); T::BurnDestination::on_unbalanced(credit); Self::deposit_event(RawEvent::Burnt(burn)) } // Must never be an error, but better to be safe. // proof: budget_remaining is account free balance minus ED; // Thus we can't spend more than account free balance minus ED; // Thus account is kept alive; qed; if let Err(problem) = T::Currency::settle( &account_id, imbalance, WithdrawReasons::TRANSFER, 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)); total_weight } /// Return the amount of money in the pot. // The existential deposit is not part of the pot so treasury account never gets deleted. pub fn pot() -> BalanceOf { T::Currency::free_balance(&Self::account_id()) // Must never be less than 0 but better be safe. .saturating_sub(T::Currency::minimum_balance()) } } impl, I: Instance> OnUnbalanced> for Module { fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { let numeric_amount = amount.peek(); // Must resolve into existing but better to be safe. let _ = T::Currency::resolve_creating(&Self::account_id(), amount); Self::deposit_event(RawEvent::Deposit(numeric_amount)); } }