referrerpolicy=no-referrer-when-downgrade

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