Skip to main content

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