pallet_treasury/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! > Made with *Substrate*, for *Polkadot*.
19//!
20//! [![github]](https://github.com/paritytech/substrate/frame/fast-unstake) -
21//! [![polkadot]](https://polkadot.network)
22//!
23//! [polkadot]: https://img.shields.io/badge/polkadot-E6007A?style=for-the-badge&logo=polkadot&logoColor=white
24//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
25//!
26//! # Treasury Pallet
27//!
28//! The Treasury pallet provides a "pot" of funds that can be managed by stakeholders in the system
29//! and a structure for making spending proposals from this pot.
30//!
31//! ## Overview
32//!
33//! The Treasury Pallet itself provides the pot to store funds, and a means for stakeholders to
34//! propose and claim expenditures (aka spends). The chain will need to provide a method to approve
35//! spends (e.g. public referendum) and a method for collecting funds (e.g. inflation, fees).
36//!
37//! By way of example, stakeholders could vote to fund the Treasury with a portion of the block
38//! reward and use the funds to pay developers.
39//!
40//! ### Terminology
41//!
42//! - **Proposal:** A suggestion to allocate funds from the pot to a beneficiary.
43//! - **Beneficiary:** An account who will receive the funds from a proposal iff the proposal is
44//!   approved.
45//! - **Pot:** Unspent funds accumulated by the treasury pallet.
46//!
47//! ## Pallet API
48//!
49//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
50//! including its configuration trait, dispatchables, storage items, events and errors.
51//!
52
53#![cfg_attr(not(feature = "std"), no_std)]
54
55mod benchmarking;
56#[cfg(test)]
57mod tests;
58pub mod weights;
59use core::marker::PhantomData;
60
61extern crate alloc;
62
63use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
64use scale_info::TypeInfo;
65
66use sp_runtime::{
67    traits::{AccountIdConversion, Saturating, StaticLookup, Zero},
68    Permill, RuntimeDebug,
69};
70
71use frame_support::{
72    dispatch::DispatchResult,
73    ensure, print,
74    traits::{
75        Currency, ExistenceRequirement::KeepAlive, Get, Imbalance, OnUnbalanced,
76        ReservableCurrency, WithdrawReasons,
77    },
78    weights::Weight,
79    PalletId,
80};
81
82pub use pallet::*;
83pub use weights::WeightInfo;
84
85pub type BalanceOf<T, I = ()> =
86    <<T as Config<I>>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
87pub type PositiveImbalanceOf<T, I = ()> = <<T as Config<I>>::Currency as Currency<
88    <T as frame_system::Config>::AccountId,
89>>::PositiveImbalance;
90pub type NegativeImbalanceOf<T, I = ()> = <<T as Config<I>>::Currency as Currency<
91    <T as frame_system::Config>::AccountId,
92>>::NegativeImbalance;
93type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
94
95/// A trait to allow the Treasury Pallet to spend it's funds for other purposes.
96/// There is an expectation that the implementer of this trait will correctly manage
97/// the mutable variables passed to it:
98/// * `budget_remaining`: How much available funds that can be spent by the treasury. As funds are
99///   spent, you must correctly deduct from this value.
100/// * `imbalance`: Any imbalances that you create should be subsumed in here to maximize efficiency
101///   of updating the total issuance. (i.e. `deposit_creating`)
102/// * `total_weight`: Track any weight that your `spend_fund` implementation uses by updating this
103///   value.
104/// * `missed_any`: If there were items that you want to spend on, but there were not enough funds,
105///   mark this value as `true`. This will prevent the treasury from burning the excess funds.
106#[impl_trait_for_tuples::impl_for_tuples(30)]
107pub trait SpendFunds<T: Config<I>, I: 'static = ()> {
108    fn spend_funds(
109        budget_remaining: &mut BalanceOf<T, I>,
110        imbalance: &mut PositiveImbalanceOf<T, I>,
111        total_weight: &mut Weight,
112        missed_any: &mut bool,
113    );
114}
115
116/// An index of a proposal. Just a `u32`.
117pub type ProposalIndex = u32;
118
119/// A spending proposal.
120#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
121#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)]
122pub struct Proposal<AccountId, Balance> {
123    /// The account proposing it.
124    proposer: AccountId,
125    /// The (total) amount that should be paid if the proposal is accepted.
126    value: Balance,
127    /// The account to whom the payment should be made if the proposal is accepted.
128    beneficiary: AccountId,
129    /// The amount held on deposit (reserved) for making this proposal.
130    bond: Balance,
131}
132
133#[frame_support::pallet]
134pub mod pallet {
135    use super::*;
136    use frame_support::pallet_prelude::*;
137    use frame_system::pallet_prelude::*;
138
139    #[pallet::pallet]
140    pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
141
142    #[pallet::config]
143    pub trait Config<I: 'static = ()>: frame_system::Config {
144        /// The staking balance.
145        type Currency: Currency<Self::AccountId> + ReservableCurrency<Self::AccountId>;
146
147        /// Origin from which approvals must come.
148        type ApproveOrigin: EnsureOrigin<Self::RuntimeOrigin>;
149
150        /// Origin from which rejections must come.
151        type RejectOrigin: EnsureOrigin<Self::RuntimeOrigin>;
152
153        /// The overarching event type.
154        #[allow(deprecated)]
155        type RuntimeEvent: From<Event<Self, I>>
156            + IsType<<Self as frame_system::Config>::RuntimeEvent>;
157
158        /// Handler for the unbalanced decrease when slashing for a rejected proposal or bounty.
159        type OnSlash: OnUnbalanced<NegativeImbalanceOf<Self, I>>;
160
161        /// Fraction of a proposal's value that should be bonded in order to place the proposal.
162        /// An accepted proposal gets these back. A rejected proposal does not.
163        #[pallet::constant]
164        type ProposalBond: Get<Permill>;
165
166        /// Minimum amount of funds that should be placed in a deposit for making a proposal.
167        #[pallet::constant]
168        type ProposalBondMinimum: Get<BalanceOf<Self, I>>;
169
170        /// Maximum amount of funds that should be placed in a deposit for making a proposal.
171        #[pallet::constant]
172        type ProposalBondMaximum: Get<Option<BalanceOf<Self, I>>>;
173
174        /// Period between successive spends.
175        #[pallet::constant]
176        type SpendPeriod: Get<BlockNumberFor<Self>>;
177
178        /// Percentage of spare funds (if any) that are burnt per spend period.
179        #[pallet::constant]
180        type Burn: Get<Permill>;
181
182        /// The treasury's pallet id, used for deriving its sovereign account ID.
183        #[pallet::constant]
184        type PalletId: Get<PalletId>;
185
186        /// Handler for the unbalanced decrease when treasury funds are burned.
187        type BurnDestination: OnUnbalanced<NegativeImbalanceOf<Self, I>>;
188
189        /// Weight information for extrinsics in this pallet.
190        type WeightInfo: WeightInfo;
191
192        /// Runtime hooks to external pallet using treasury to compute spend funds.
193        type SpendFunds: SpendFunds<Self, I>;
194
195        /// The maximum number of approvals that can wait in the spending queue.
196        ///
197        /// NOTE: This parameter is also used within the Bounties Pallet extension if enabled.
198        #[pallet::constant]
199        type MaxApprovals: Get<u32>;
200    }
201
202    /// Number of proposals that have been made.
203    #[pallet::storage]
204    #[pallet::getter(fn proposal_count)]
205    pub(crate) type ProposalCount<T, I = ()> = StorageValue<_, ProposalIndex, ValueQuery>;
206
207    /// Proposals that have been made.
208    #[pallet::storage]
209    #[pallet::getter(fn proposals)]
210    pub type Proposals<T: Config<I>, I: 'static = ()> = StorageMap<
211        _,
212        Twox64Concat,
213        ProposalIndex,
214        Proposal<T::AccountId, BalanceOf<T, I>>,
215        OptionQuery,
216    >;
217
218    /// The amount which has been reported as inactive to Currency.
219    #[pallet::storage]
220    pub type Deactivated<T: Config<I>, I: 'static = ()> =
221        StorageValue<_, BalanceOf<T, I>, ValueQuery>;
222
223    /// Proposal indices that have been approved but not yet awarded.
224    #[pallet::storage]
225    #[pallet::getter(fn approvals)]
226    pub type Approvals<T: Config<I>, I: 'static = ()> =
227        StorageValue<_, BoundedVec<ProposalIndex, T::MaxApprovals>, ValueQuery>;
228
229    #[pallet::genesis_config]
230    #[derive(frame_support::DefaultNoBound)]
231    pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
232        #[serde(skip)]
233        _config: core::marker::PhantomData<(T, I)>,
234    }
235
236    #[pallet::genesis_build]
237    impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
238        fn build(&self) {
239            // Create Treasury account
240            let account_id = <Pallet<T, I>>::account_id();
241            let min = T::Currency::minimum_balance();
242            if T::Currency::free_balance(&account_id) < min {
243                let _ = T::Currency::make_free_balance_be(&account_id, min);
244            }
245        }
246    }
247
248    #[pallet::event]
249    #[pallet::generate_deposit(pub(super) fn deposit_event)]
250    #[repr(u8)]
251    pub enum Event<T: Config<I>, I: 'static = ()> {
252        /// New proposal.
253        Proposed { proposal_index: ProposalIndex } = 0,
254        /// We have ended a spend period and will now allocate funds.
255        Spending { budget_remaining: BalanceOf<T, I> } = 1,
256        /// Some funds have been allocated.
257        Awarded {
258            proposal_index: ProposalIndex,
259            award: BalanceOf<T, I>,
260            account: T::AccountId,
261        } = 2,
262        /// A proposal was rejected; funds were slashed.
263        Rejected {
264            proposal_index: ProposalIndex,
265            slashed: BalanceOf<T, I>,
266        } = 3,
267        /// Some of our funds have been burnt.
268        Burnt { burnt_funds: BalanceOf<T, I> } = 4,
269        /// Spending has finished; this is the amount that rolls over until next spend.
270        Rollover { rollover_balance: BalanceOf<T, I> } = 5,
271        /// Some funds have been deposited.
272        Deposit { value: BalanceOf<T, I> } = 6,
273        /// A new spend proposal has been approved.
274        /// The inactive funds of the pallet have been updated.
275        UpdatedInactive {
276            reactivated: BalanceOf<T, I>,
277            deactivated: BalanceOf<T, I>,
278        } = 8,
279    }
280
281    /// Error for the treasury pallet.
282    #[pallet::error]
283    pub enum Error<T, I = ()> {
284        /// Proposer's balance is too low.
285        InsufficientProposersBalance,
286        /// No proposal, bounty or spend at that index.
287        InvalidIndex,
288        /// Too many approvals in the queue.
289        TooManyApprovals,
290        /// The spend origin is valid but the amount it is allowed to spend is lower than the
291        /// amount to be spent.
292        InsufficientPermission,
293        /// Proposal has not been approved.
294        ProposalNotApproved,
295    }
296
297    #[pallet::hooks]
298    impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
299        /// ## Complexity
300        /// - `O(A)` where `A` is the number of approvals
301        fn on_initialize(n: frame_system::pallet_prelude::BlockNumberFor<T>) -> Weight {
302            let pot = Self::pot();
303            let deactivated = Deactivated::<T, I>::get();
304            if pot != deactivated {
305                T::Currency::reactivate(deactivated);
306                T::Currency::deactivate(pot);
307                Deactivated::<T, I>::put(pot);
308                Self::deposit_event(Event::<T, I>::UpdatedInactive {
309                    reactivated: deactivated,
310                    deactivated: pot,
311                });
312            }
313
314            // Check to see if we should spend some funds!
315            if (n % T::SpendPeriod::get()).is_zero() {
316                Self::spend_funds()
317            } else {
318                Weight::zero()
319            }
320        }
321
322        #[cfg(feature = "try-runtime")]
323        fn try_state(
324            _: frame_system::pallet_prelude::BlockNumberFor<T>,
325        ) -> Result<(), sp_runtime::TryRuntimeError> {
326            Self::do_try_state()?;
327            Ok(())
328        }
329    }
330
331    #[pallet::call]
332    impl<T: Config<I>, I: 'static> Pallet<T, I> {
333        /// Put forward a suggestion for spending.
334        ///
335        /// ## Dispatch Origin
336        ///
337        /// Must be signed.
338        ///
339        /// ## Details
340        /// A deposit proportional to the value is reserved and slashed if the proposal is rejected.
341        /// It is returned once the proposal is awarded.
342        ///
343        /// ### Complexity
344        /// - O(1)
345        ///
346        /// ## Events
347        ///
348        /// Emits [`Event::Proposed`] if successful.
349        #[pallet::call_index(0)]
350        #[pallet::weight(T::WeightInfo::propose_spend())]
351        #[allow(deprecated)]
352        #[deprecated(
353            note = "`propose_spend` will be removed in February 2024. Use `spend` instead."
354        )]
355        pub fn propose_spend(
356            origin: OriginFor<T>,
357            #[pallet::compact] value: BalanceOf<T, I>,
358            beneficiary: AccountIdLookupOf<T>,
359        ) -> DispatchResult {
360            let proposer = ensure_signed(origin)?;
361            let beneficiary = T::Lookup::lookup(beneficiary)?;
362
363            let bond = Self::calculate_bond(value);
364            T::Currency::reserve(&proposer, bond)
365                .map_err(|_| Error::<T, I>::InsufficientProposersBalance)?;
366
367            let c = Self::proposal_count();
368            <ProposalCount<T, I>>::put(c + 1);
369            <Proposals<T, I>>::insert(
370                c,
371                Proposal {
372                    proposer,
373                    value,
374                    beneficiary,
375                    bond,
376                },
377            );
378
379            Self::deposit_event(Event::Proposed { proposal_index: c });
380            Ok(())
381        }
382
383        /// Reject a proposed spend.
384        ///
385        /// ## Dispatch Origin
386        ///
387        /// Must be [`Config::RejectOrigin`].
388        ///
389        /// ## Details
390        /// The original deposit will be slashed.
391        ///
392        /// ### Complexity
393        /// - O(1)
394        ///
395        /// ## Events
396        ///
397        /// Emits [`Event::Rejected`] if successful.
398        #[pallet::call_index(1)]
399        #[pallet::weight((T::WeightInfo::reject_proposal(), DispatchClass::Operational))]
400        #[allow(deprecated)]
401        #[deprecated(
402            note = "`reject_proposal` will be removed in February 2024. Use `spend` instead."
403        )]
404        pub fn reject_proposal(
405            origin: OriginFor<T>,
406            #[pallet::compact] proposal_id: ProposalIndex,
407        ) -> DispatchResult {
408            T::RejectOrigin::ensure_origin(origin)?;
409
410            let proposal =
411                <Proposals<T, I>>::take(proposal_id).ok_or(Error::<T, I>::InvalidIndex)?;
412            let value = proposal.bond;
413            let imbalance = T::Currency::slash_reserved(&proposal.proposer, value).0;
414            T::OnSlash::on_unbalanced(imbalance);
415
416            Self::deposit_event(Event::<T, I>::Rejected {
417                proposal_index: proposal_id,
418                slashed: value,
419            });
420            Ok(())
421        }
422
423        /// Approve a proposal.
424        ///
425        /// ## Dispatch Origin
426        ///
427        /// Must be [`Config::ApproveOrigin`].
428        ///
429        /// ## Details
430        ///
431        /// At a later time, the proposal will be allocated to the beneficiary and the original
432        /// deposit will be returned.
433        ///
434        /// ### Complexity
435        ///  - O(1).
436        ///
437        /// ## Events
438        ///
439        /// No events are emitted from this dispatch.
440        #[pallet::call_index(2)]
441        #[pallet::weight((T::WeightInfo::approve_proposal(T::MaxApprovals::get()), DispatchClass::Operational))]
442        #[allow(deprecated)]
443        #[deprecated(
444            note = "`approve_proposal` will be removed in February 2024. Use `spend` instead."
445        )]
446        pub fn approve_proposal(
447            origin: OriginFor<T>,
448            #[pallet::compact] proposal_id: ProposalIndex,
449        ) -> DispatchResult {
450            T::ApproveOrigin::ensure_origin(origin)?;
451
452            ensure!(
453                <Proposals<T, I>>::contains_key(proposal_id),
454                Error::<T, I>::InvalidIndex
455            );
456            Approvals::<T, I>::try_append(proposal_id)
457                .map_err(|_| Error::<T, I>::TooManyApprovals)?;
458            Ok(())
459        }
460    }
461}
462
463impl<T: Config<I>, I: 'static> Pallet<T, I> {
464    // Add public immutables and private mutables.
465
466    /// The account ID of the treasury pot.
467    ///
468    /// This actually does computation. If you need to keep using it, then make sure you cache the
469    /// value and only call this once.
470    pub fn account_id() -> T::AccountId {
471        T::PalletId::get().into_account_truncating()
472    }
473
474    /// The needed bond for a proposal whose spend is `value`.
475    fn calculate_bond(value: BalanceOf<T, I>) -> BalanceOf<T, I> {
476        let mut r = T::ProposalBondMinimum::get().max(T::ProposalBond::get() * value);
477        if let Some(m) = T::ProposalBondMaximum::get() {
478            r = r.min(m);
479        }
480        r
481    }
482
483    /// Spend some money! returns number of approvals before spend.
484    pub fn spend_funds() -> Weight {
485        let mut total_weight = Weight::zero();
486
487        let mut budget_remaining = Self::pot();
488        Self::deposit_event(Event::Spending { budget_remaining });
489        let account_id = Self::account_id();
490
491        let mut missed_any = false;
492        let mut imbalance = <PositiveImbalanceOf<T, I>>::zero();
493        let proposals_len = Approvals::<T, I>::mutate(|v| {
494            let proposals_approvals_len = v.len() as u32;
495            v.retain(|&index| {
496                // Should always be true, but shouldn't panic if false or we're screwed.
497                if let Some(p) = Self::proposals(index) {
498                    if p.value <= budget_remaining {
499                        budget_remaining -= p.value;
500                        <Proposals<T, I>>::remove(index);
501
502                        // return their deposit.
503                        let err_amount = T::Currency::unreserve(&p.proposer, p.bond);
504                        debug_assert!(err_amount.is_zero());
505
506                        // provide the allocation.
507                        imbalance.subsume(T::Currency::deposit_creating(&p.beneficiary, p.value));
508
509                        Self::deposit_event(Event::Awarded {
510                            proposal_index: index,
511                            award: p.value,
512                            account: p.beneficiary,
513                        });
514                        false
515                    } else {
516                        missed_any = true;
517                        true
518                    }
519                } else {
520                    false
521                }
522            });
523            proposals_approvals_len
524        });
525
526        total_weight += T::WeightInfo::on_initialize_proposals(proposals_len);
527
528        // Call Runtime hooks to external pallet using treasury to compute spend funds.
529        T::SpendFunds::spend_funds(
530            &mut budget_remaining,
531            &mut imbalance,
532            &mut total_weight,
533            &mut missed_any,
534        );
535
536        if !missed_any {
537            // burn some proportion of the remaining budget if we run a surplus.
538            let burn = (T::Burn::get() * budget_remaining).min(budget_remaining);
539            budget_remaining -= burn;
540
541            let (debit, credit) = T::Currency::pair(burn);
542            imbalance.subsume(debit);
543            T::BurnDestination::on_unbalanced(credit);
544            Self::deposit_event(Event::Burnt { burnt_funds: burn })
545        }
546
547        // Must never be an error, but better to be safe.
548        // proof: budget_remaining is account free balance minus ED;
549        // Thus we can't spend more than account free balance minus ED;
550        // Thus account is kept alive; qed;
551        if let Err(problem) =
552            T::Currency::settle(&account_id, imbalance, WithdrawReasons::TRANSFER, KeepAlive)
553        {
554            print("Inconsistent state - couldn't settle imbalance for funds spent by treasury");
555            // Nothing else to do here.
556            drop(problem);
557        }
558
559        Self::deposit_event(Event::Rollover {
560            rollover_balance: budget_remaining,
561        });
562
563        total_weight
564    }
565
566    /// Return the amount of money in the pot.
567    // The existential deposit is not part of the pot so treasury account never gets deleted.
568    pub fn pot() -> BalanceOf<T, I> {
569        T::Currency::free_balance(&Self::account_id())
570            // Must never be less than 0 but better be safe.
571            .saturating_sub(T::Currency::minimum_balance())
572    }
573
574    /// Ensure the correctness of the state of this pallet.
575    #[cfg(any(feature = "try-runtime", test))]
576    fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
577        Self::try_state_proposals()?;
578        Ok(())
579    }
580
581    /// ### Invariants of proposal storage items
582    ///
583    /// 1. [`ProposalCount`] >= Number of elements in [`Proposals`].
584    /// 2. Each entry in [`Proposals`] should be saved under a key strictly less than current
585    ///    [`ProposalCount`].
586    /// 3. Each [`ProposalIndex`] contained in [`Approvals`] should exist in [`Proposals`].
587    ///    Note, that this automatically implies [`Approvals`].count() <= [`Proposals`].count().
588    #[cfg(any(feature = "try-runtime", test))]
589    fn try_state_proposals() -> Result<(), sp_runtime::TryRuntimeError> {
590        let current_proposal_count = ProposalCount::<T, I>::get();
591        ensure!(
592            current_proposal_count as usize >= Proposals::<T, I>::iter().count(),
593            "Actual number of proposals exceeds `ProposalCount`."
594        );
595
596        Proposals::<T, I>::iter_keys().try_for_each(|proposal_index| -> DispatchResult {
597            ensure!(
598				current_proposal_count > proposal_index,
599				"`ProposalCount` should by strictly greater than any ProposalIndex used as a key for `Proposals`."
600			);
601            Ok(())
602        })?;
603
604        Approvals::<T, I>::get()
605            .iter()
606            .try_for_each(|proposal_index| -> DispatchResult {
607                ensure!(
608                    Proposals::<T, I>::contains_key(proposal_index),
609                    "Proposal indices in `Approvals` must also be contained in `Proposals`."
610                );
611                Ok(())
612            })?;
613
614        Ok(())
615    }
616}
617
618impl<T: Config<I>, I: 'static> OnUnbalanced<NegativeImbalanceOf<T, I>> for Pallet<T, I> {
619    fn on_nonzero_unbalanced(amount: NegativeImbalanceOf<T, I>) {
620        let numeric_amount = amount.peek();
621
622        // Must resolve into existing but better to be safe.
623        T::Currency::resolve_creating(&Self::account_id(), amount);
624
625        Self::deposit_event(Event::Deposit {
626            value: numeric_amount,
627        });
628    }
629}
630
631/// TypedGet implementation to get the AccountId of the Treasury.
632pub struct TreasuryAccountId<R>(PhantomData<R>);
633impl<R> sp_runtime::traits::TypedGet for TreasuryAccountId<R>
634where
635    R: crate::Config,
636{
637    type Type = <R as frame_system::Config>::AccountId;
638    fn get() -> Self::Type {
639        <crate::Pallet<R>>::account_id()
640    }
641}