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//! This is copied from [![github]](https://github.com/paritytech/polkadot-sdk/tree/release-polkadot-v1.13.0)
18//! Git Hash d5160c1d567cc73c7df6c816d41e21aa3adb188d
19//! > Made with *Substrate*, for *Polkadot*.
20//!
21//! [![github]](https://github.com/paritytech/substrate/frame/fast-unstake) -
22//! [![polkadot]](https://polkadot.network)
23//!
24//! [polkadot]: https://img.shields.io/badge/polkadot-E6007A?style=for-the-badge&logo=polkadot&logoColor=white
25//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
26//!
27//! # Treasury Pallet
28//!
29//! The Treasury pallet provides a "pot" of funds that can be managed by stakeholders in the system
30//! and a structure for making spending proposals from this pot.
31//!
32//! ## Overview
33//!
34//! The Treasury Pallet itself provides the pot to store funds, and a means for stakeholders to
35//! propose and claim expenditures (aka spends). The chain will need to provide a method to approve
36//! spends (e.g. public referendum) and a method for collecting funds (e.g. inflation, fees).
37//!
38//! By way of example, stakeholders could vote to fund the Treasury with a portion of the block
39//! reward and use the funds to pay developers.
40//!
41//! ### Terminology
42//!
43//! - **Proposal:** A suggestion to allocate funds from the pot to a beneficiary.
44//! - **Beneficiary:** An account who will receive the funds from a proposal iff the proposal is
45//!   approved.
46//! - **Pot:** Unspent funds accumulated by the treasury pallet.
47//! - **Spend** An approved proposal for transferring a specific amount of funds to a designated
48//!   beneficiary.
49//!
50//! ### Example
51//!
52//! 1. Multiple local spends approved by spend origins and received by a beneficiary.
53#![doc = docify::embed!("src/tests.rs", spend_local_origin_works)]
54//!
55//! 2. Approve a spend of some asset kind and claim it.
56#![doc = docify::embed!("src/tests.rs", spend_payout_works)]
57//!
58//! ## Pallet API
59//!
60//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
61//! including its configuration trait, dispatchables, storage items, events and errors.
62//!
63//! ## Low Level / Implementation Details
64//!
65//! Spends can be initiated using either the `spend_local` or `spend` dispatchable. The
66//! `spend_local` dispatchable enables the creation of spends using the native currency of the
67//! chain, utilizing the funds stored in the pot. These spends are automatically paid out every
68//! [`pallet::Config::SpendPeriod`]. On the other hand, the `spend` dispatchable allows spending of
69//! any asset kind managed by the treasury, with payment facilitated by a designated
70//! [`pallet::Config::Paymaster`]. To claim these spends, the `payout` dispatchable should be called
71//! within some temporal bounds, starting from the moment they become valid and within one
72//! [`pallet::Config::PayoutPeriod`].
73
74#![cfg_attr(not(feature = "std"), no_std)]
75#![allow(clippy::expect_used)]
76
77mod benchmarking;
78#[cfg(test)]
79mod tests;
80pub mod weights;
81use core::marker::PhantomData;
82
83#[cfg(feature = "runtime-benchmarks")]
84pub use benchmarking::ArgumentsFactory;
85
86use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
87use scale_info::TypeInfo;
88
89use sp_runtime::{
90	traits::{AccountIdConversion, CheckedAdd, Saturating, StaticLookup, Zero},
91	Permill, RuntimeDebug,
92};
93extern crate alloc;
94use alloc::{boxed::Box, collections::btree_map::BTreeMap};
95
96use frame_support::{
97	dispatch::{DispatchResult, DispatchResultWithPostInfo},
98	ensure, print,
99	traits::{
100		tokens::Pay, Currency, ExistenceRequirement::KeepAlive, Get, Imbalance, OnUnbalanced,
101		ReservableCurrency, WithdrawReasons,
102	},
103	weights::Weight,
104	PalletId,
105};
106
107pub use pallet::*;
108pub use weights::WeightInfo;
109
110pub type BalanceOf<T, I = ()> =
111	<<T as Config<I>>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
112pub type AssetBalanceOf<T, I> = <<T as Config<I>>::Paymaster as Pay>::Balance;
113pub type PositiveImbalanceOf<T, I = ()> = <<T as Config<I>>::Currency as Currency<
114	<T as frame_system::Config>::AccountId,
115>>::PositiveImbalance;
116pub type NegativeImbalanceOf<T, I = ()> = <<T as Config<I>>::Currency as Currency<
117	<T as frame_system::Config>::AccountId,
118>>::NegativeImbalance;
119type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
120type BeneficiaryLookupOf<T, I> = <<T as Config<I>>::BeneficiaryLookup as StaticLookup>::Source;
121
122/// A trait to allow the Treasury Pallet to spend it's funds for other purposes.
123/// There is an expectation that the implementer of this trait will correctly manage
124/// the mutable variables passed to it:
125/// * `budget_remaining`: How much available funds that can be spent by the treasury. As funds are
126///   spent, you must correctly deduct from this value.
127/// * `imbalance`: Any imbalances that you create should be subsumed in here to maximize efficiency
128///   of updating the total issuance. (i.e. `deposit_creating`)
129/// * `total_weight`: Track any weight that your `spend_fund` implementation uses by updating this
130///   value.
131/// * `missed_any`: If there were items that you want to spend on, but there were not enough funds,
132///   mark this value as `true`. This will prevent the treasury from burning the excess funds.
133#[impl_trait_for_tuples::impl_for_tuples(30)]
134pub trait SpendFunds<T: Config<I>, I: 'static = ()> {
135	fn spend_funds(
136		budget_remaining: &mut BalanceOf<T, I>,
137		imbalance: &mut PositiveImbalanceOf<T, I>,
138		total_weight: &mut Weight,
139		missed_any: &mut bool,
140	);
141}
142
143/// An index of a proposal. Just a `u32`.
144pub type ProposalIndex = u32;
145
146/// A spending proposal.
147#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
148#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)]
149pub struct Proposal<AccountId, Balance> {
150	/// The account proposing it.
151	proposer: AccountId,
152	/// The (total) amount that should be paid if the proposal is accepted.
153	value: Balance,
154	/// The account to whom the payment should be made if the proposal is accepted.
155	beneficiary: AccountId,
156	/// The amount held on deposit (reserved) for making this proposal.
157	bond: Balance,
158}
159
160/// The state of the payment claim.
161#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
162#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)]
163pub enum PaymentState<Id> {
164	/// Pending claim.
165	Pending,
166	/// Payment attempted with a payment identifier.
167	Attempted { id: Id },
168	/// Payment failed.
169	Failed,
170}
171
172/// Info regarding an approved treasury spend.
173#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
174#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)]
175pub struct SpendStatus<AssetKind, AssetBalance, Beneficiary, BlockNumber, PaymentId> {
176	// The kind of asset to be spent.
177	asset_kind: AssetKind,
178	/// The asset amount of the spend.
179	amount: AssetBalance,
180	/// The beneficiary of the spend.
181	beneficiary: Beneficiary,
182	/// The block number from which the spend can be claimed.
183	valid_from: BlockNumber,
184	/// The block number by which the spend has to be claimed.
185	expire_at: BlockNumber,
186	/// The status of the payout/claim.
187	status: PaymentState<PaymentId>,
188}
189
190/// Index of an approved treasury spend.
191pub type SpendIndex = u32;
192
193#[frame_support::pallet]
194pub mod pallet {
195	use super::*;
196	use frame_support::{
197		dispatch_context::with_context,
198		pallet_prelude::*,
199		traits::tokens::{ConversionFromAssetBalance, PaymentStatus},
200	};
201	use frame_system::pallet_prelude::*;
202
203	#[pallet::pallet]
204	pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
205
206	#[pallet::config]
207	pub trait Config<I: 'static = ()>: frame_system::Config {
208		/// The staking balance.
209		type Currency: Currency<Self::AccountId> + ReservableCurrency<Self::AccountId>;
210
211		/// Origin from which approvals must come.
212		type ApproveOrigin: EnsureOrigin<Self::RuntimeOrigin>;
213
214		/// Origin from which rejections must come.
215		type RejectOrigin: EnsureOrigin<Self::RuntimeOrigin>;
216
217		/// The overarching event type.
218		#[allow(deprecated)]
219		type RuntimeEvent: From<Event<Self, I>>
220			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
221
222		/// Handler for the unbalanced decrease when slashing for a rejected proposal or bounty.
223		type OnSlash: OnUnbalanced<NegativeImbalanceOf<Self, I>>;
224
225		/// Fraction of a proposal's value that should be bonded in order to place the proposal.
226		/// An accepted proposal gets these back. A rejected proposal does not.
227		#[pallet::constant]
228		type ProposalBond: Get<Permill>;
229
230		/// Minimum amount of funds that should be placed in a deposit for making a proposal.
231		#[pallet::constant]
232		type ProposalBondMinimum: Get<BalanceOf<Self, I>>;
233
234		/// Maximum amount of funds that should be placed in a deposit for making a proposal.
235		#[pallet::constant]
236		type ProposalBondMaximum: Get<Option<BalanceOf<Self, I>>>;
237
238		/// Period between successive spends.
239		#[pallet::constant]
240		type SpendPeriod: Get<BlockNumberFor<Self>>;
241
242		/// Percentage of spare funds (if any) that are burnt per spend period.
243		#[pallet::constant]
244		type Burn: Get<Permill>;
245
246		/// The treasury's pallet id, used for deriving its sovereign account ID.
247		#[pallet::constant]
248		type PalletId: Get<PalletId>;
249
250		/// Handler for the unbalanced decrease when treasury funds are burned.
251		type BurnDestination: OnUnbalanced<NegativeImbalanceOf<Self, I>>;
252
253		/// Weight information for extrinsics in this pallet.
254		type WeightInfo: WeightInfo;
255
256		/// Runtime hooks to external pallet using treasury to compute spend funds.
257		type SpendFunds: SpendFunds<Self, I>;
258
259		/// The maximum number of approvals that can wait in the spending queue.
260		///
261		/// NOTE: This parameter is also used within the Bounties Pallet extension if enabled.
262		#[pallet::constant]
263		type MaxApprovals: Get<u32>;
264
265		/// The origin required for approving spends from the treasury outside of the proposal
266		/// process. The `Success` value is the maximum amount in a native asset that this origin
267		/// is allowed to spend at a time.
268		type SpendOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = BalanceOf<Self, I>>;
269
270		/// Type parameter representing the asset kinds to be spent from the treasury.
271		type AssetKind: Parameter + MaxEncodedLen;
272
273		/// Type parameter used to identify the beneficiaries eligible to receive treasury spends.
274		type Beneficiary: Parameter + MaxEncodedLen;
275
276		/// Converting trait to take a source type and convert to [`Self::Beneficiary`].
277		type BeneficiaryLookup: StaticLookup<Target = Self::Beneficiary>;
278
279		/// Type for processing spends of [Self::AssetKind] in favor of [`Self::Beneficiary`].
280		type Paymaster: Pay<Beneficiary = Self::Beneficiary, AssetKind = Self::AssetKind>;
281
282		/// Type for converting the balance of an [Self::AssetKind] to the balance of the native
283		/// asset, solely for the purpose of asserting the result against the maximum allowed spend
284		/// amount of the [`Self::SpendOrigin`].
285		type BalanceConverter: ConversionFromAssetBalance<
286			<Self::Paymaster as Pay>::Balance,
287			Self::AssetKind,
288			BalanceOf<Self, I>,
289		>;
290
291		/// The period during which an approved treasury spend has to be claimed.
292		#[pallet::constant]
293		type PayoutPeriod: Get<BlockNumberFor<Self>>;
294
295		/// Helper type for benchmarks.
296		#[cfg(feature = "runtime-benchmarks")]
297		type BenchmarkHelper: ArgumentsFactory<Self::AssetKind, Self::Beneficiary>;
298	}
299
300	/// Number of proposals that have been made.
301	#[pallet::storage]
302	#[pallet::getter(fn proposal_count)]
303	pub(crate) type ProposalCount<T, I = ()> = StorageValue<_, ProposalIndex, ValueQuery>;
304
305	/// Proposals that have been made.
306	#[pallet::storage]
307	#[pallet::getter(fn proposals)]
308	pub type Proposals<T: Config<I>, I: 'static = ()> = StorageMap<
309		_,
310		Twox64Concat,
311		ProposalIndex,
312		Proposal<T::AccountId, BalanceOf<T, I>>,
313		OptionQuery,
314	>;
315
316	/// The amount which has been reported as inactive to Currency.
317	#[pallet::storage]
318	pub type Deactivated<T: Config<I>, I: 'static = ()> =
319		StorageValue<_, BalanceOf<T, I>, ValueQuery>;
320
321	/// Proposal indices that have been approved but not yet awarded.
322	#[pallet::storage]
323	#[pallet::getter(fn approvals)]
324	pub type Approvals<T: Config<I>, I: 'static = ()> =
325		StorageValue<_, BoundedVec<ProposalIndex, T::MaxApprovals>, ValueQuery>;
326
327	/// The count of spends that have been made.
328	#[pallet::storage]
329	pub(crate) type SpendCount<T, I = ()> = StorageValue<_, SpendIndex, ValueQuery>;
330
331	/// Spends that have been approved and being processed.
332	// Hasher: Twox safe since `SpendIndex` is an internal count based index.
333	#[pallet::storage]
334	pub type Spends<T: Config<I>, I: 'static = ()> = StorageMap<
335		_,
336		Twox64Concat,
337		SpendIndex,
338		SpendStatus<
339			T::AssetKind,
340			AssetBalanceOf<T, I>,
341			T::Beneficiary,
342			BlockNumberFor<T>,
343			<T::Paymaster as Pay>::Id,
344		>,
345		OptionQuery,
346	>;
347
348	#[pallet::genesis_config]
349	#[derive(frame_support::DefaultNoBound)]
350	pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
351		#[serde(skip)]
352		_config: PhantomData<(T, I)>,
353	}
354
355	#[pallet::genesis_build]
356	impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
357		fn build(&self) {
358			// Create Treasury account
359			let account_id = <Pallet<T, I>>::account_id();
360			let min = T::Currency::minimum_balance();
361			if T::Currency::free_balance(&account_id) < min {
362				let _ = T::Currency::make_free_balance_be(&account_id, min);
363			}
364		}
365	}
366
367	#[pallet::event]
368	#[pallet::generate_deposit(pub(super) fn deposit_event)]
369	pub enum Event<T: Config<I>, I: 'static = ()> {
370		/// New proposal.
371		Proposed { proposal_index: ProposalIndex },
372		/// We have ended a spend period and will now allocate funds.
373		Spending { budget_remaining: BalanceOf<T, I> },
374		/// Some funds have been allocated.
375		Awarded { proposal_index: ProposalIndex, award: BalanceOf<T, I>, account: T::AccountId },
376		/// A proposal was rejected; funds were slashed.
377		Rejected { proposal_index: ProposalIndex, slashed: BalanceOf<T, I> },
378		/// Some of our funds have been burnt.
379		Burnt { burnt_funds: BalanceOf<T, I> },
380		/// Spending has finished; this is the amount that rolls over until next spend.
381		Rollover { rollover_balance: BalanceOf<T, I> },
382		/// Some funds have been deposited.
383		Deposit { value: BalanceOf<T, I> },
384		/// A new spend proposal has been approved.
385		SpendApproved {
386			proposal_index: ProposalIndex,
387			amount: BalanceOf<T, I>,
388			beneficiary: T::AccountId,
389		},
390		/// The inactive funds of the pallet have been updated.
391		UpdatedInactive { reactivated: BalanceOf<T, I>, deactivated: BalanceOf<T, I> },
392		/// A new asset spend proposal has been approved.
393		AssetSpendApproved {
394			index: SpendIndex,
395			asset_kind: T::AssetKind,
396			amount: AssetBalanceOf<T, I>,
397			beneficiary: T::Beneficiary,
398			valid_from: BlockNumberFor<T>,
399			expire_at: BlockNumberFor<T>,
400		},
401		/// An approved spend was voided.
402		AssetSpendVoided { index: SpendIndex },
403		/// A payment happened.
404		Paid { index: SpendIndex, payment_id: <T::Paymaster as Pay>::Id },
405		/// A payment failed and can be retried.
406		PaymentFailed { index: SpendIndex, payment_id: <T::Paymaster as Pay>::Id },
407		/// A spend was processed and removed from the storage. It might have been successfully
408		/// paid or it may have expired.
409		SpendProcessed { index: SpendIndex },
410	}
411
412	/// Error for the treasury pallet.
413	#[pallet::error]
414	pub enum Error<T, I = ()> {
415		/// Proposer's balance is too low.
416		InsufficientProposersBalance,
417		/// No proposal, bounty or spend at that index.
418		InvalidIndex,
419		/// Too many approvals in the queue.
420		TooManyApprovals,
421		/// The spend origin is valid but the amount it is allowed to spend is lower than the
422		/// amount to be spent.
423		InsufficientPermission,
424		/// Proposal has not been approved.
425		ProposalNotApproved,
426		/// The balance of the asset kind is not convertible to the balance of the native asset.
427		FailedToConvertBalance,
428		/// The spend has expired and cannot be claimed.
429		SpendExpired,
430		/// The spend is not yet eligible for payout.
431		EarlyPayout,
432		/// The payment has already been attempted.
433		AlreadyAttempted,
434		/// There was some issue with the mechanism of payment.
435		PayoutError,
436		/// The payout was not yet attempted/claimed.
437		NotAttempted,
438		/// The payment has neither failed nor succeeded yet.
439		Inconclusive,
440	}
441
442	#[pallet::hooks]
443	impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
444		/// ## Complexity
445		/// - `O(A)` where `A` is the number of approvals
446		fn on_initialize(n: frame_system::pallet_prelude::BlockNumberFor<T>) -> Weight {
447			let pot = Self::pot();
448			let deactivated = Deactivated::<T, I>::get();
449			if pot != deactivated {
450				T::Currency::reactivate(deactivated);
451				T::Currency::deactivate(pot);
452				Deactivated::<T, I>::put(pot);
453				Self::deposit_event(Event::<T, I>::UpdatedInactive {
454					reactivated: deactivated,
455					deactivated: pot,
456				});
457			}
458
459			// Check to see if we should spend some funds!
460			if (n % T::SpendPeriod::get()).is_zero() {
461				Self::spend_funds()
462			} else {
463				Weight::zero()
464			}
465		}
466
467		#[cfg(feature = "try-runtime")]
468		fn try_state(
469			_: frame_system::pallet_prelude::BlockNumberFor<T>,
470		) -> Result<(), sp_runtime::TryRuntimeError> {
471			Self::do_try_state()?;
472			Ok(())
473		}
474	}
475
476	#[derive(Default)]
477	struct SpendContext<Balance> {
478		spend_in_context: BTreeMap<Balance, Balance>,
479	}
480
481	#[pallet::call]
482	impl<T: Config<I>, I: 'static> Pallet<T, I> {
483		/// Put forward a suggestion for spending.
484		///
485		/// ## Dispatch Origin
486		///
487		/// Must be signed.
488		///
489		/// ## Details
490		/// A deposit proportional to the value is reserved and slashed if the proposal is rejected.
491		/// It is returned once the proposal is awarded.
492		///
493		/// ### Complexity
494		/// - O(1)
495		///
496		/// ## Events
497		///
498		/// Emits [`Event::Proposed`] if successful.
499		#[pallet::call_index(0)]
500		#[pallet::weight(T::WeightInfo::propose_spend())]
501		#[allow(deprecated)]
502		#[deprecated(
503			note = "`propose_spend` will be removed in February 2024. Use `spend` instead."
504		)]
505		pub fn propose_spend(
506			origin: OriginFor<T>,
507			#[pallet::compact] value: BalanceOf<T, I>,
508			beneficiary: AccountIdLookupOf<T>,
509		) -> DispatchResult {
510			let proposer = ensure_signed(origin)?;
511			let beneficiary = T::Lookup::lookup(beneficiary)?;
512
513			let bond = Self::calculate_bond(value);
514			T::Currency::reserve(&proposer, bond)
515				.map_err(|_| Error::<T, I>::InsufficientProposersBalance)?;
516
517			let c = Self::proposal_count();
518			<ProposalCount<T, I>>::put(c + 1);
519			<Proposals<T, I>>::insert(c, Proposal { proposer, value, beneficiary, bond });
520
521			Self::deposit_event(Event::Proposed { proposal_index: c });
522			Ok(())
523		}
524
525		/// Reject a proposed spend.
526		///
527		/// ## Dispatch Origin
528		///
529		/// Must be [`Config::RejectOrigin`].
530		///
531		/// ## Details
532		/// The original deposit will be slashed.
533		///
534		/// ### Complexity
535		/// - O(1)
536		///
537		/// ## Events
538		///
539		/// Emits [`Event::Rejected`] if successful.
540		#[pallet::call_index(1)]
541		#[pallet::weight((T::WeightInfo::reject_proposal(), DispatchClass::Operational))]
542		#[allow(deprecated)]
543		#[deprecated(
544			note = "`reject_proposal` will be removed in February 2024. Use `spend` instead."
545		)]
546		pub fn reject_proposal(
547			origin: OriginFor<T>,
548			#[pallet::compact] proposal_id: ProposalIndex,
549		) -> DispatchResult {
550			T::RejectOrigin::ensure_origin(origin)?;
551
552			let proposal =
553				<Proposals<T, I>>::take(proposal_id).ok_or(Error::<T, I>::InvalidIndex)?;
554			let value = proposal.bond;
555			let imbalance = T::Currency::slash_reserved(&proposal.proposer, value).0;
556			T::OnSlash::on_unbalanced(imbalance);
557
558			Self::deposit_event(Event::<T, I>::Rejected {
559				proposal_index: proposal_id,
560				slashed: value,
561			});
562			Ok(())
563		}
564
565		/// Approve a proposal.
566		///
567		/// ## Dispatch Origin
568		///
569		/// Must be [`Config::ApproveOrigin`].
570		///
571		/// ## Details
572		///
573		/// At a later time, the proposal will be allocated to the beneficiary and the original
574		/// deposit will be returned.
575		///
576		/// ### Complexity
577		///  - O(1).
578		///
579		/// ## Events
580		///
581		/// No events are emitted from this dispatch.
582		#[pallet::call_index(2)]
583		#[pallet::weight((T::WeightInfo::approve_proposal(T::MaxApprovals::get()), DispatchClass::Operational))]
584		#[allow(deprecated)]
585		#[deprecated(
586			note = "`approve_proposal` will be removed in February 2024. Use `spend` instead."
587		)]
588		pub fn approve_proposal(
589			origin: OriginFor<T>,
590			#[pallet::compact] proposal_id: ProposalIndex,
591		) -> DispatchResult {
592			T::ApproveOrigin::ensure_origin(origin)?;
593
594			ensure!(<Proposals<T, I>>::contains_key(proposal_id), Error::<T, I>::InvalidIndex);
595			Approvals::<T, I>::try_append(proposal_id)
596				.map_err(|_| Error::<T, I>::TooManyApprovals)?;
597			Ok(())
598		}
599
600		/// Propose and approve a spend of treasury funds.
601		///
602		/// ## Dispatch Origin
603		///
604		/// Must be [`Config::SpendOrigin`] with the `Success` value being at least `amount`.
605		///
606		/// ### Details
607		/// NOTE: For record-keeping purposes, the proposer is deemed to be equivalent to the
608		/// beneficiary.
609		///
610		/// ### Parameters
611		/// - `amount`: The amount to be transferred from the treasury to the `beneficiary`.
612		/// - `beneficiary`: The destination account for the transfer.
613		///
614		/// ## Events
615		///
616		/// Emits [`Event::SpendApproved`] if successful.
617		#[pallet::call_index(3)]
618		#[pallet::weight(T::WeightInfo::spend_local())]
619		pub fn spend_local(
620			origin: OriginFor<T>,
621			#[pallet::compact] amount: BalanceOf<T, I>,
622			beneficiary: AccountIdLookupOf<T>,
623		) -> DispatchResult {
624			let max_amount = T::SpendOrigin::ensure_origin(origin)?;
625			ensure!(amount <= max_amount, Error::<T, I>::InsufficientPermission);
626
627			with_context::<SpendContext<BalanceOf<T, I>>, _>(|v| {
628				let context = v.or_default();
629
630				// We group based on `max_amount`, to distinguish between different kind of
631				// origins. (assumes that all origins have different `max_amount`)
632				//
633				// Worst case is that we reject some "valid" request.
634				let spend = context.spend_in_context.entry(max_amount).or_default();
635
636				// Ensure that we don't overflow nor use more than `max_amount`
637				if spend.checked_add(&amount).map(|s| s > max_amount).unwrap_or(true) {
638					Err(Error::<T, I>::InsufficientPermission)
639				} else {
640					*spend = spend.saturating_add(amount);
641
642					Ok(())
643				}
644			})
645			.unwrap_or(Ok(()))?;
646
647			let beneficiary = T::Lookup::lookup(beneficiary)?;
648			let proposal_index = Self::proposal_count();
649			Approvals::<T, I>::try_append(proposal_index)
650				.map_err(|_| Error::<T, I>::TooManyApprovals)?;
651			let proposal = Proposal {
652				proposer: beneficiary.clone(),
653				value: amount,
654				beneficiary: beneficiary.clone(),
655				bond: Default::default(),
656			};
657			Proposals::<T, I>::insert(proposal_index, proposal);
658			ProposalCount::<T, I>::put(proposal_index + 1);
659
660			Self::deposit_event(Event::SpendApproved { proposal_index, amount, beneficiary });
661			Ok(())
662		}
663
664		/// Force a previously approved proposal to be removed from the approval queue.
665		///
666		/// ## Dispatch Origin
667		///
668		/// Must be [`Config::RejectOrigin`].
669		///
670		/// ## Details
671		///
672		/// The original deposit will no longer be returned.
673		///
674		/// ### Parameters
675		/// - `proposal_id`: The index of a proposal
676		///
677		/// ### Complexity
678		/// - O(A) where `A` is the number of approvals
679		///
680		/// ### Errors
681		/// - [`Error::ProposalNotApproved`]: The `proposal_id` supplied was not found in the
682		///   approval queue, i.e., the proposal has not been approved. This could also mean the
683		///   proposal does not exist altogether, thus there is no way it would have been approved
684		///   in the first place.
685		#[pallet::call_index(4)]
686		#[pallet::weight((T::WeightInfo::remove_approval(), DispatchClass::Operational))]
687		pub fn remove_approval(
688			origin: OriginFor<T>,
689			#[pallet::compact] proposal_id: ProposalIndex,
690		) -> DispatchResult {
691			T::RejectOrigin::ensure_origin(origin)?;
692
693			Approvals::<T, I>::try_mutate(|v| -> DispatchResult {
694				if let Some(index) = v.iter().position(|x| x == &proposal_id) {
695					v.remove(index);
696					Ok(())
697				} else {
698					Err(Error::<T, I>::ProposalNotApproved.into())
699				}
700			})?;
701
702			Ok(())
703		}
704
705		/// Propose and approve a spend of treasury funds.
706		///
707		/// ## Dispatch Origin
708		///
709		/// Must be [`Config::SpendOrigin`] with the `Success` value being at least
710		/// `amount` of `asset_kind` in the native asset. The amount of `asset_kind` is converted
711		/// for assertion using the [`Config::BalanceConverter`].
712		///
713		/// ## Details
714		///
715		/// Create an approved spend for transferring a specific `amount` of `asset_kind` to a
716		/// designated beneficiary. The spend must be claimed using the `payout` dispatchable within
717		/// the [`Config::PayoutPeriod`].
718		///
719		/// ### Parameters
720		/// - `asset_kind`: An indicator of the specific asset class to be spent.
721		/// - `amount`: The amount to be transferred from the treasury to the `beneficiary`.
722		/// - `beneficiary`: The beneficiary of the spend.
723		/// - `valid_from`: The block number from which the spend can be claimed. It can refer to
724		///   the past if the resulting spend has not yet expired according to the
725		///   [`Config::PayoutPeriod`]. If `None`, the spend can be claimed immediately after
726		///   approval.
727		///
728		/// ## Events
729		///
730		/// Emits [`Event::AssetSpendApproved`] if successful.
731		#[pallet::call_index(5)]
732		#[pallet::weight(T::WeightInfo::spend())]
733		pub fn spend(
734			origin: OriginFor<T>,
735			asset_kind: Box<T::AssetKind>,
736			#[pallet::compact] amount: AssetBalanceOf<T, I>,
737			beneficiary: Box<BeneficiaryLookupOf<T, I>>,
738			valid_from: Option<BlockNumberFor<T>>,
739		) -> DispatchResult {
740			let max_amount = T::SpendOrigin::ensure_origin(origin)?;
741			let beneficiary = T::BeneficiaryLookup::lookup(*beneficiary)?;
742
743			let now = frame_system::Pallet::<T>::block_number();
744			let valid_from = valid_from.unwrap_or(now);
745			let expire_at = valid_from.saturating_add(T::PayoutPeriod::get());
746			ensure!(expire_at > now, Error::<T, I>::SpendExpired);
747
748			let native_amount =
749				T::BalanceConverter::from_asset_balance(amount, *asset_kind.clone())
750					.map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
751
752			ensure!(native_amount <= max_amount, Error::<T, I>::InsufficientPermission);
753
754			with_context::<SpendContext<BalanceOf<T, I>>, _>(|v| {
755				let context = v.or_default();
756				// We group based on `max_amount`, to distinguish between different kind of
757				// origins. (assumes that all origins have different `max_amount`)
758				//
759				// Worst case is that we reject some "valid" request.
760				let spend = context.spend_in_context.entry(max_amount).or_default();
761
762				// Ensure that we don't overflow nor use more than `max_amount`
763				if spend.checked_add(&native_amount).map(|s| s > max_amount).unwrap_or(true) {
764					Err(Error::<T, I>::InsufficientPermission)
765				} else {
766					*spend = spend.saturating_add(native_amount);
767					Ok(())
768				}
769			})
770			.unwrap_or(Ok(()))?;
771
772			let index = SpendCount::<T, I>::get();
773			Spends::<T, I>::insert(
774				index,
775				SpendStatus {
776					asset_kind: *asset_kind.clone(),
777					amount,
778					beneficiary: beneficiary.clone(),
779					valid_from,
780					expire_at,
781					status: PaymentState::Pending,
782				},
783			);
784			SpendCount::<T, I>::put(index + 1);
785
786			Self::deposit_event(Event::AssetSpendApproved {
787				index,
788				asset_kind: *asset_kind,
789				amount,
790				beneficiary,
791				valid_from,
792				expire_at,
793			});
794			Ok(())
795		}
796
797		/// Claim a spend.
798		///
799		/// ## Dispatch Origin
800		///
801		/// Must be signed.
802		///
803		/// ## Details
804		///
805		/// Spends must be claimed within some temporal bounds. A spend may be claimed within one
806		/// [`Config::PayoutPeriod`] from the `valid_from` block.
807		/// In case of a payout failure, the spend status must be updated with the `check_status`
808		/// dispatchable before retrying with the current function.
809		///
810		/// ### Parameters
811		/// - `index`: The spend index.
812		///
813		/// ## Events
814		///
815		/// Emits [`Event::Paid`] if successful.
816		#[pallet::call_index(6)]
817		#[pallet::weight(T::WeightInfo::payout())]
818		pub fn payout(origin: OriginFor<T>, index: SpendIndex) -> DispatchResult {
819			ensure_signed(origin)?;
820			let mut spend = Spends::<T, I>::get(index).ok_or(Error::<T, I>::InvalidIndex)?;
821			let now = frame_system::Pallet::<T>::block_number();
822			ensure!(now >= spend.valid_from, Error::<T, I>::EarlyPayout);
823			ensure!(spend.expire_at > now, Error::<T, I>::SpendExpired);
824			ensure!(
825				matches!(spend.status, PaymentState::Pending | PaymentState::Failed),
826				Error::<T, I>::AlreadyAttempted
827			);
828
829			let id = T::Paymaster::pay(&spend.beneficiary, spend.asset_kind.clone(), spend.amount)
830				.map_err(|_| Error::<T, I>::PayoutError)?;
831
832			spend.status = PaymentState::Attempted { id };
833			Spends::<T, I>::insert(index, spend);
834
835			Self::deposit_event(Event::<T, I>::Paid { index, payment_id: id });
836
837			Ok(())
838		}
839
840		/// Check the status of the spend and remove it from the storage if processed.
841		///
842		/// ## Dispatch Origin
843		///
844		/// Must be signed.
845		///
846		/// ## Details
847		///
848		/// The status check is a prerequisite for retrying a failed payout.
849		/// If a spend has either succeeded or expired, it is removed from the storage by this
850		/// function. In such instances, transaction fees are refunded.
851		///
852		/// ### Parameters
853		/// - `index`: The spend index.
854		///
855		/// ## Events
856		///
857		/// Emits [`Event::PaymentFailed`] if the spend payout has failed.
858		/// Emits [`Event::SpendProcessed`] if the spend payout has succeed.
859		#[pallet::call_index(7)]
860		#[pallet::weight(T::WeightInfo::check_status())]
861		#[allow(clippy::useless_conversion)]
862		pub fn check_status(origin: OriginFor<T>, index: SpendIndex) -> DispatchResultWithPostInfo {
863			use PaymentState as State;
864			use PaymentStatus as Status;
865
866			ensure_signed(origin)?;
867			let mut spend = Spends::<T, I>::get(index).ok_or(Error::<T, I>::InvalidIndex)?;
868			let now = frame_system::Pallet::<T>::block_number();
869
870			if now > spend.expire_at && !matches!(spend.status, State::Attempted { .. }) {
871				// spend has expired and no further status update is expected.
872				Spends::<T, I>::remove(index);
873				Self::deposit_event(Event::<T, I>::SpendProcessed { index });
874				return Ok(Pays::No.into());
875			}
876
877			let payment_id = match spend.status {
878				State::Attempted { id } => id,
879				_ => return Err(Error::<T, I>::NotAttempted.into()),
880			};
881
882			match T::Paymaster::check_payment(payment_id) {
883				Status::Failure => {
884					spend.status = PaymentState::Failed;
885					Spends::<T, I>::insert(index, spend);
886					Self::deposit_event(Event::<T, I>::PaymentFailed { index, payment_id });
887				},
888				Status::Success | Status::Unknown => {
889					Spends::<T, I>::remove(index);
890					Self::deposit_event(Event::<T, I>::SpendProcessed { index });
891					return Ok(Pays::No.into());
892				},
893				Status::InProgress => return Err(Error::<T, I>::Inconclusive.into()),
894			}
895			return Ok(Pays::Yes.into());
896		}
897
898		/// Void previously approved spend.
899		///
900		/// ## Dispatch Origin
901		///
902		/// Must be [`Config::RejectOrigin`].
903		///
904		/// ## Details
905		///
906		/// A spend void is only possible if the payout has not been attempted yet.
907		///
908		/// ### Parameters
909		/// - `index`: The spend index.
910		///
911		/// ## Events
912		///
913		/// Emits [`Event::AssetSpendVoided`] if successful.
914		#[pallet::call_index(8)]
915		#[pallet::weight(T::WeightInfo::void_spend())]
916		pub fn void_spend(origin: OriginFor<T>, index: SpendIndex) -> DispatchResult {
917			T::RejectOrigin::ensure_origin(origin)?;
918			let spend = Spends::<T, I>::get(index).ok_or(Error::<T, I>::InvalidIndex)?;
919			ensure!(
920				matches!(spend.status, PaymentState::Pending | PaymentState::Failed),
921				Error::<T, I>::AlreadyAttempted
922			);
923
924			Spends::<T, I>::remove(index);
925			Self::deposit_event(Event::<T, I>::AssetSpendVoided { index });
926			Ok(())
927		}
928	}
929}
930
931impl<T: Config<I>, I: 'static> Pallet<T, I> {
932	// Add public immutables and private mutables.
933
934	/// The account ID of the treasury pot.
935	///
936	/// This actually does computation. If you need to keep using it, then make sure you cache the
937	/// value and only call this once.
938	pub fn account_id() -> T::AccountId {
939		T::PalletId::get().into_account_truncating()
940	}
941
942	/// The needed bond for a proposal whose spend is `value`.
943	fn calculate_bond(value: BalanceOf<T, I>) -> BalanceOf<T, I> {
944		let mut r = T::ProposalBondMinimum::get().max(T::ProposalBond::get() * value);
945		if let Some(m) = T::ProposalBondMaximum::get() {
946			r = r.min(m);
947		}
948		r
949	}
950
951	/// Spend some money! returns number of approvals before spend.
952	pub fn spend_funds() -> Weight {
953		let mut total_weight = Weight::zero();
954
955		let mut budget_remaining = Self::pot();
956		Self::deposit_event(Event::Spending { budget_remaining });
957		let account_id = Self::account_id();
958
959		let mut missed_any = false;
960		let mut imbalance = <PositiveImbalanceOf<T, I>>::zero();
961		let proposals_len = Approvals::<T, I>::mutate(|v| {
962			let proposals_approvals_len = v.len() as u32;
963			v.retain(|&index| {
964				// Should always be true, but shouldn't panic if false or we're screwed.
965				if let Some(p) = Self::proposals(index) {
966					if p.value <= budget_remaining {
967						budget_remaining -= p.value;
968						<Proposals<T, I>>::remove(index);
969
970						// return their deposit.
971						let err_amount = T::Currency::unreserve(&p.proposer, p.bond);
972						debug_assert!(err_amount.is_zero());
973
974						// provide the allocation.
975						imbalance.subsume(T::Currency::deposit_creating(&p.beneficiary, p.value));
976
977						Self::deposit_event(Event::Awarded {
978							proposal_index: index,
979							award: p.value,
980							account: p.beneficiary,
981						});
982						false
983					} else {
984						missed_any = true;
985						true
986					}
987				} else {
988					false
989				}
990			});
991			proposals_approvals_len
992		});
993
994		total_weight += T::WeightInfo::on_initialize_proposals(proposals_len);
995
996		// Call Runtime hooks to external pallet using treasury to compute spend funds.
997		T::SpendFunds::spend_funds(
998			&mut budget_remaining,
999			&mut imbalance,
1000			&mut total_weight,
1001			&mut missed_any,
1002		);
1003
1004		if !missed_any {
1005			// burn some proportion of the remaining budget if we run a surplus.
1006			let burn = (T::Burn::get() * budget_remaining).min(budget_remaining);
1007			budget_remaining -= burn;
1008
1009			let (debit, credit) = T::Currency::pair(burn);
1010			imbalance.subsume(debit);
1011			T::BurnDestination::on_unbalanced(credit);
1012			Self::deposit_event(Event::Burnt { burnt_funds: burn })
1013		}
1014
1015		// Must never be an error, but better to be safe.
1016		// proof: budget_remaining is account free balance minus ED;
1017		// Thus we can't spend more than account free balance minus ED;
1018		// Thus account is kept alive; qed;
1019		if let Err(problem) =
1020			T::Currency::settle(&account_id, imbalance, WithdrawReasons::TRANSFER, KeepAlive)
1021		{
1022			print("Inconsistent state - couldn't settle imbalance for funds spent by treasury");
1023			// Nothing else to do here.
1024			drop(problem);
1025		}
1026
1027		Self::deposit_event(Event::Rollover { rollover_balance: budget_remaining });
1028
1029		total_weight
1030	}
1031
1032	/// Return the amount of money in the pot.
1033	// The existential deposit is not part of the pot so treasury account never gets deleted.
1034	pub fn pot() -> BalanceOf<T, I> {
1035		T::Currency::free_balance(&Self::account_id())
1036			// Must never be less than 0 but better be safe.
1037			.saturating_sub(T::Currency::minimum_balance())
1038	}
1039
1040	/// Ensure the correctness of the state of this pallet.
1041	#[cfg(any(feature = "try-runtime", test))]
1042	fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
1043		Self::try_state_proposals()?;
1044		Self::try_state_spends()?;
1045
1046		Ok(())
1047	}
1048
1049	/// ### Invariants of proposal storage items
1050	///
1051	/// 1. [`ProposalCount`] >= Number of elements in [`Proposals`].
1052	/// 2. Each entry in [`Proposals`] should be saved under a key strictly less than current [`ProposalCount`].
1053	/// 3. Each [`ProposalIndex`] contained in [`Approvals`] should exist in [`Proposals`].
1054	///
1055	/// Note, that this automatically implies [`Approvals`].count() <= [`Proposals`].count().
1056	#[cfg(any(feature = "try-runtime", test))]
1057	fn try_state_proposals() -> Result<(), sp_runtime::TryRuntimeError> {
1058		let current_proposal_count = ProposalCount::<T, I>::get();
1059		ensure!(
1060			current_proposal_count as usize >= Proposals::<T, I>::iter().count(),
1061			"Actual number of proposals exceeds `ProposalCount`."
1062		);
1063
1064		Proposals::<T, I>::iter_keys().try_for_each(|proposal_index| -> DispatchResult {
1065			ensure!(
1066				current_proposal_count > proposal_index,
1067				"`ProposalCount` should by strictly greater than any ProposalIndex used as a key for `Proposals`."
1068			);
1069			Ok(())
1070		})?;
1071
1072		Approvals::<T, I>::get()
1073			.iter()
1074			.try_for_each(|proposal_index| -> DispatchResult {
1075				ensure!(
1076					Proposals::<T, I>::contains_key(proposal_index),
1077					"Proposal indices in `Approvals` must also be contained in `Proposals`."
1078				);
1079				Ok(())
1080			})?;
1081
1082		Ok(())
1083	}
1084
1085	/// ## Invariants of spend storage items
1086	///
1087	/// 1. [`SpendCount`] >= Number of elements in [`Spends`].
1088	/// 2. Each entry in [`Spends`] should be saved under a key strictly less than current [`SpendCount`].
1089	/// 3. For each spend entry contained in [`Spends`] we should have spend.expire_at
1090	/// > spend.valid_from.
1091	#[cfg(any(feature = "try-runtime", test))]
1092	fn try_state_spends() -> Result<(), sp_runtime::TryRuntimeError> {
1093		let current_spend_count = SpendCount::<T, I>::get();
1094		ensure!(
1095			current_spend_count as usize >= Spends::<T, I>::iter().count(),
1096			"Actual number of spends exceeds `SpendCount`."
1097		);
1098
1099		Spends::<T, I>::iter_keys().try_for_each(|spend_index| -> DispatchResult {
1100			ensure!(
1101				current_spend_count > spend_index,
1102				"`SpendCount` should by strictly greater than any SpendIndex used as a key for `Spends`."
1103			);
1104			Ok(())
1105		})?;
1106
1107		Spends::<T, I>::iter().try_for_each(|(_index, spend)| -> DispatchResult {
1108			ensure!(
1109				spend.valid_from < spend.expire_at,
1110				"Spend cannot expire before it becomes valid."
1111			);
1112			Ok(())
1113		})?;
1114
1115		Ok(())
1116	}
1117}
1118
1119impl<T: Config<I>, I: 'static> OnUnbalanced<NegativeImbalanceOf<T, I>> for Pallet<T, I> {
1120	fn on_nonzero_unbalanced(amount: NegativeImbalanceOf<T, I>) {
1121		let numeric_amount = amount.peek();
1122
1123		// Must resolve into existing but better to be safe.
1124		T::Currency::resolve_creating(&Self::account_id(), amount);
1125
1126		Self::deposit_event(Event::Deposit { value: numeric_amount });
1127	}
1128}
1129
1130/// TypedGet implementation to get the AccountId of the Treasury.
1131pub struct TreasuryAccountId<R>(PhantomData<R>);
1132impl<R> sp_runtime::traits::TypedGet for TreasuryAccountId<R>
1133where
1134	R: crate::Config,
1135{
1136	type Type = <R as frame_system::Config>::AccountId;
1137	fn get() -> Self::Type {
1138		<crate::Pallet<R>>::account_id()
1139	}
1140}