referrerpolicy=no-referrer-when-downgrade

frame_support/traits/tokens/fungible/
mod.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//! The traits for dealing with a single fungible token class and any associated types.
19//!
20//! Also see the [`frame_tokens`] reference docs for more information about the place of
21//! `fungible` traits in Substrate.
22//!
23//! # Available Traits
24//! - [`Inspect`]: Regular balance inspector functions.
25//! - [`Unbalanced`]: Low-level balance mutating functions. Does not guarantee proper book-keeping
26//!   and so should not be called into directly from application code. Other traits depend on this
27//!   and provide default implementations based on it.
28//! - [`UnbalancedHold`]: Low-level balance mutating functions for balances placed on hold. Does not
29//!   guarantee proper book-keeping and so should not be called into directly from application code.
30//!   Other traits depend on this and provide default implementations based on it.
31//! - [`Mutate`]: Regular balance mutator functions. Pre-implemented using [`Unbalanced`], though
32//!   the `done_*` functions should likely be reimplemented in case you want to do something
33//!   following the operation such as emit events.
34//! - [`InspectHold`]: Inspector functions for balances on hold.
35//! - [`MutateHold`]: Mutator functions for balances on hold. Mostly pre-implemented using
36//!   [`UnbalancedHold`].
37//! - [`InspectFreeze`]: Inspector functions for frozen balance.
38//! - [`MutateFreeze`]: Mutator functions for frozen balance.
39//! - [`Balanced`]: One-sided mutator functions for regular balances, which return imbalance objects
40//!   which guarantee eventual book-keeping. May be useful for some sophisticated operations where
41//!   funds must be removed from an account before it is known precisely what should be done with
42//!   them.
43//! - [`metadata::Inspect`]: Inspector functions for token metadata (name, symbol, decimals).
44//! - [`metadata::Mutate`]: Mutator functions for token metadata.
45//! - [`lifetime::Create`]: Trait for creating a new fungible asset.
46//!
47//! ## Terminology
48//!
49//! - **Total Issuance**: The total number of units in existence in a system.
50//!
51//! - **Total Balance**: The sum of an account's free and held balances.
52//!
53//! - **Free Balance**: A portion of an account's total balance that is not held. Note this is
54//!   distinct from the Spendable Balance, which represents how much Balance the user can actually
55//!   transfer.
56//!
57//! - **Held Balance**: Held balance still belongs to the account holder, but is suspended — it
58//!   cannot be transferred or used for most operations. It may be slashed by the pallet that placed
59//!   the hold.
60//!
61//!   Multiple holds stack rather than overlay. This means that if an account has
62//!   3 holds for 100 units, the account can spend its funds for any reason down to 300 units, at
63//!   which point the holds will start to come into play.
64//!
65//! - **Frozen Balance**: A freeze on a specified amount of an account's balance. Tokens that are
66//!   frozen cannot be transferred.
67//!
68//!   Multiple freezes always operate over the same funds, so they "overlay" rather than
69//!   "stack". This means that if an account has 3 freezes for 100 units, the account can spend its
70//!   funds for any reason down to 100 units, at which point the freezes will start to come into
71//!   play.
72//!
73//!   It's important to note that the frozen balance can exceed the total balance of the account.
74//!   This is useful, eg, in cases where you want to prevent a user from transferring any fund. In
75//!   such a case, setting the frozen balance to `Balance::MAX` would serve that purpose
76//! effectively.
77//!
78//! - **Minimum Balance (a.k.a. Existential Deposit, a.k.a. ED)**: The minimum balance required to
79//!   create or keep an account open. This is to prevent "dust accounts" from filling storage. When
80//!   the free plus the held balance (i.e. the total balance) falls below this, then the account is
81//!   said to be dead. It loses its functionality as well as any prior history and all information
82//!   on it is removed from the chain's state. No account should ever have a total balance that is
83//!   strictly between 0 and the existential deposit (exclusive). If this ever happens, it indicates
84//!   either a bug in the implementation of this trait or an erroneous raw mutation of storage.
85//!
86//! - **Untouchable Balance**: The part of a user's free balance they cannot spend, due to ED or
87//!   Freeze(s).
88//!
89//! - **Spendable Balance**: The part of a user's free balance they can actually transfer, after
90//!   accounting for Holds and Freezes.
91//!
92//! - **Imbalance**: A condition when some funds were credited or debited without equal and opposite
93//!   accounting (i.e. a difference between total issuance and account balances). Functions that
94//!   result in an imbalance will return an object of the [`imbalance::Credit`] or
95//!   [`imbalance::Debt`] traits that can be managed within your runtime logic.
96//!
97//!   If an imbalance is simply dropped, it should automatically maintain any book-keeping such as
98//!   total issuance.
99//!
100//! ## Visualising Balance Components Together 💫
101//!
102//! ```ignore
103//! |__total__________________________________|
104//! |__on_hold__|_____________free____________|
105//! |__________frozen___________|
106//! |__on_hold__|__ed__|
107//!             |__untouchable__|__spendable__|
108//! ```
109//!
110//! ## Holds and Freezes
111//!
112//! Both holds and freezes are used to prevent an account from using some of its balance.
113//!
114//! The primary distinction between the two are that:
115//! - Holds are cumulative (do not overlap) and are distinct from the free balance
116//! - Freezes are not cumulative, and can overlap with each other or with holds
117//!
118//! ```ignore
119//! |__total_____________________________|
120//! |__hold_a__|__hold_b__|_____free_____|
121//! |__on_hold____________|     // <- the sum of all holds
122//! |__freeze_a_______________|
123//! |__freeze_b____|
124//! |__freeze_c________|
125//! |__frozen_________________| // <- the max of all freezes
126//! ```
127//!
128//! Holds are designed to be infallibly slashed, meaning that any logic using a `Freeze`
129//! must handle the possibility of the frozen amount being reduced, potentially to zero. A
130//! permissionless function should be provided in order to allow bookkeeping to be updated in this
131//! instance. E.g. some balance is frozen when it is used for voting, one could use held balance for
132//! voting, but nothing prevents this frozen balance from being reduced if the overlapping hold is
133//! slashed.
134//!
135//! Every Hold and Freeze is accompanied by a unique `Reason`, making it clear for each instance
136//! what the originating pallet and purpose is. These reasons are amalgomated into a single enum
137//! `RuntimeHoldReason` and `RuntimeFreezeReason` respectively, when the runtime is compiled.
138//!
139//! Note that `Hold` and `Freeze` reasons should remain in your runtime for as long as storage
140//! could exist in your runtime with those reasons, otherwise your runtime state could become
141//! undecodable.
142//!
143//! ### Should I use a Hold or Freeze?
144//!
145//! If you require a balance to be infaillibly slashed, then you should use Holds.
146//!
147//! If you require setting a minimum account balance amount, then you should use a Freezes. Note
148//! Freezes do not carry the same guarantees as Holds. Although the account cannot voluntarily
149//! reduce their balance below the largest freeze, if Holds on the account are slashed then the
150//! balance could drop below the freeze amount.
151//!
152//! ## Sets of Tokens
153//!
154//! For managing sets of tokens, see the [`fungibles`](`frame_support::traits::fungibles`) trait
155//! which is a wrapper around this trait but supporting multiple asset instances.
156//!
157//! [`frame_tokens`]: ../../../../polkadot_sdk_docs/reference_docs/frame_tokens/index.html
158
159pub mod conformance_tests;
160pub mod freeze;
161pub mod hold;
162pub(crate) mod imbalance;
163mod item_of;
164mod lifetime;
165pub mod metadata;
166mod regular;
167mod union_of;
168
169use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
170use core::marker::PhantomData;
171use frame_support_procedural::{CloneNoBound, DebugNoBound, EqNoBound, PartialEqNoBound};
172use scale_info::TypeInfo;
173#[cfg(feature = "runtime-benchmarks")]
174use sp_runtime::Saturating;
175
176use super::{
177	Fortitude::{Force, Polite},
178	Precision::BestEffort,
179};
180pub use freeze::{Inspect as InspectFreeze, Mutate as MutateFreeze};
181pub use hold::{
182	Balanced as BalancedHold, Inspect as InspectHold, Mutate as MutateHold,
183	Unbalanced as UnbalancedHold,
184};
185pub use imbalance::{Credit, Debt, HandleImbalanceDrop, Imbalance};
186pub use item_of::ItemOf;
187pub use lifetime::Create;
188pub use regular::{
189	Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Mutate, Unbalanced,
190};
191use sp_arithmetic::traits::Zero;
192use sp_core::Get;
193use sp_runtime::{traits::Convert, DispatchError};
194pub use union_of::{NativeFromLeft, NativeOrWithId, UnionOf};
195
196#[cfg(feature = "experimental")]
197use crate::traits::MaybeConsideration;
198use crate::{
199	ensure,
200	traits::{Consideration, Footprint},
201};
202
203/// Consideration method using a `fungible` balance frozen as the cost exacted for the footprint.
204///
205/// The aggregate amount frozen under `R::get()` for any account which has multiple tickets,
206/// is the *cumulative* amounts of each ticket's footprint (each individually determined by `D`).
207#[derive(
208	CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, TypeInfo, MaxEncodedLen, DebugNoBound,
209)]
210#[scale_info(skip_type_params(A, F, R, D, Fp))]
211#[codec(mel_bound())]
212pub struct FreezeConsideration<A, F, R, D, Fp>(F::Balance, PhantomData<fn() -> (A, R, D, Fp)>)
213where
214	F: MutateFreeze<A>;
215impl<
216		A: 'static + Eq,
217		#[cfg(not(feature = "runtime-benchmarks"))] F: 'static + MutateFreeze<A>,
218		#[cfg(feature = "runtime-benchmarks")] F: 'static + MutateFreeze<A> + Mutate<A>,
219		R: 'static + Get<F::Id>,
220		D: 'static + Convert<Fp, F::Balance>,
221		Fp: 'static,
222	> Consideration<A, Fp> for FreezeConsideration<A, F, R, D, Fp>
223{
224	fn new(who: &A, footprint: Fp) -> Result<Self, DispatchError> {
225		let new = D::convert(footprint);
226		F::increase_frozen(&R::get(), who, new)?;
227		Ok(Self(new, PhantomData))
228	}
229	fn update(self, who: &A, footprint: Fp) -> Result<Self, DispatchError> {
230		let new = D::convert(footprint);
231		if self.0 > new {
232			F::decrease_frozen(&R::get(), who, self.0 - new)?;
233		} else if new > self.0 {
234			F::increase_frozen(&R::get(), who, new - self.0)?;
235		}
236		Ok(Self(new, PhantomData))
237	}
238	fn drop(self, who: &A) -> Result<(), DispatchError> {
239		F::decrease_frozen(&R::get(), who, self.0).map(|_| ())
240	}
241	#[cfg(feature = "runtime-benchmarks")]
242	fn ensure_successful(who: &A, fp: Fp) {
243		let _ = F::mint_into(who, F::minimum_balance().saturating_add(D::convert(fp)));
244	}
245}
246#[cfg(feature = "experimental")]
247impl<
248		A: 'static + Eq,
249		#[cfg(not(feature = "runtime-benchmarks"))] F: 'static + MutateFreeze<A>,
250		#[cfg(feature = "runtime-benchmarks")] F: 'static + MutateFreeze<A> + Mutate<A>,
251		R: 'static + Get<F::Id>,
252		D: 'static + Convert<Fp, F::Balance>,
253		Fp: 'static,
254	> MaybeConsideration<A, Fp> for FreezeConsideration<A, F, R, D, Fp>
255{
256	fn is_none(&self) -> bool {
257		self.0.is_zero()
258	}
259}
260
261/// Consideration method using a `fungible` balance frozen as the cost exacted for the footprint.
262#[derive(
263	CloneNoBound,
264	EqNoBound,
265	PartialEqNoBound,
266	Encode,
267	Decode,
268	DecodeWithMemTracking,
269	TypeInfo,
270	MaxEncodedLen,
271	DebugNoBound,
272)]
273#[scale_info(skip_type_params(A, F, R, D, Fp))]
274#[codec(mel_bound())]
275pub struct HoldConsideration<A, F, R, D, Fp = Footprint>(
276	F::Balance,
277	PhantomData<fn() -> (A, R, D, Fp)>,
278)
279where
280	F: MutateHold<A>;
281impl<
282		A: 'static + Eq,
283		#[cfg(not(feature = "runtime-benchmarks"))] F: 'static + MutateHold<A>,
284		#[cfg(feature = "runtime-benchmarks")] F: 'static + MutateHold<A> + Mutate<A>,
285		R: 'static + Get<F::Reason>,
286		D: 'static + Convert<Fp, F::Balance>,
287		Fp: 'static,
288	> Consideration<A, Fp> for HoldConsideration<A, F, R, D, Fp>
289{
290	fn new(who: &A, footprint: Fp) -> Result<Self, DispatchError> {
291		let new = D::convert(footprint);
292		F::hold(&R::get(), who, new)?;
293		Ok(Self(new, PhantomData))
294	}
295	fn update(self, who: &A, footprint: Fp) -> Result<Self, DispatchError> {
296		let new = D::convert(footprint);
297		if self.0 > new {
298			F::release(&R::get(), who, self.0 - new, BestEffort)?;
299		} else if new > self.0 {
300			F::hold(&R::get(), who, new - self.0)?;
301		}
302		Ok(Self(new, PhantomData))
303	}
304	fn drop(self, who: &A) -> Result<(), DispatchError> {
305		F::release(&R::get(), who, self.0, BestEffort).map(|_| ())
306	}
307	fn burn(self, who: &A) {
308		let _ = F::burn_held(&R::get(), who, self.0, BestEffort, Force);
309	}
310	#[cfg(feature = "runtime-benchmarks")]
311	fn ensure_successful(who: &A, fp: Fp) {
312		let _ = F::mint_into(who, F::minimum_balance().saturating_add(D::convert(fp)));
313	}
314}
315#[cfg(feature = "experimental")]
316impl<
317		A: 'static + Eq,
318		#[cfg(not(feature = "runtime-benchmarks"))] F: 'static + MutateHold<A>,
319		#[cfg(feature = "runtime-benchmarks")] F: 'static + MutateHold<A> + Mutate<A>,
320		R: 'static + Get<F::Reason>,
321		D: 'static + Convert<Fp, F::Balance>,
322		Fp: 'static,
323	> MaybeConsideration<A, Fp> for HoldConsideration<A, F, R, D, Fp>
324{
325	fn is_none(&self) -> bool {
326		self.0.is_zero()
327	}
328}
329
330/// Basic consideration method using a `fungible` balance frozen as the cost exacted for the
331/// footprint.
332///
333/// NOTE: This is an optimized implementation, which can only be used for systems where each
334/// account has only a single active ticket associated with it since individual tickets do not
335/// track the specific balance which is frozen. If you are uncertain then use `FreezeConsideration`
336/// instead, since this works in all circumstances.
337#[derive(
338	CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, TypeInfo, MaxEncodedLen, DebugNoBound,
339)]
340#[scale_info(skip_type_params(A, Fx, Rx, D, Fp))]
341#[codec(mel_bound())]
342pub struct LoneFreezeConsideration<A, Fx, Rx, D, Fp>(PhantomData<fn() -> (A, Fx, Rx, D, Fp)>);
343impl<
344		A: 'static + Eq,
345		#[cfg(not(feature = "runtime-benchmarks"))] Fx: 'static + MutateFreeze<A>,
346		#[cfg(feature = "runtime-benchmarks")] Fx: 'static + MutateFreeze<A> + Mutate<A>,
347		Rx: 'static + Get<Fx::Id>,
348		D: 'static + Convert<Fp, Fx::Balance>,
349		Fp: 'static,
350	> Consideration<A, Fp> for LoneFreezeConsideration<A, Fx, Rx, D, Fp>
351{
352	fn new(who: &A, footprint: Fp) -> Result<Self, DispatchError> {
353		ensure!(Fx::balance_frozen(&Rx::get(), who).is_zero(), DispatchError::Unavailable);
354		Fx::set_frozen(&Rx::get(), who, D::convert(footprint), Polite).map(|_| Self(PhantomData))
355	}
356	fn update(self, who: &A, footprint: Fp) -> Result<Self, DispatchError> {
357		Fx::set_frozen(&Rx::get(), who, D::convert(footprint), Polite).map(|_| Self(PhantomData))
358	}
359	fn drop(self, who: &A) -> Result<(), DispatchError> {
360		Fx::thaw(&Rx::get(), who).map(|_| ())
361	}
362	#[cfg(feature = "runtime-benchmarks")]
363	fn ensure_successful(who: &A, fp: Fp) {
364		let _ = Fx::mint_into(who, Fx::minimum_balance().saturating_add(D::convert(fp)));
365	}
366}
367
368/// Basic consideration method using a `fungible` balance placed on hold as the cost exacted for the
369/// footprint.
370///
371/// NOTE: This is an optimized implementation, which can only be used for systems where each
372/// account has only a single active ticket associated with it since individual tickets do not
373/// track the specific balance which is frozen. If you are uncertain then use `FreezeConsideration`
374/// instead, since this works in all circumstances.
375#[derive(
376	CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, TypeInfo, MaxEncodedLen, DebugNoBound,
377)]
378#[scale_info(skip_type_params(A, Fx, Rx, D, Fp))]
379#[codec(mel_bound())]
380pub struct LoneHoldConsideration<A, Fx, Rx, D, Fp>(PhantomData<fn() -> (A, Fx, Rx, D, Fp)>);
381impl<
382		A: 'static + Eq,
383		#[cfg(not(feature = "runtime-benchmarks"))] F: 'static + MutateHold<A>,
384		#[cfg(feature = "runtime-benchmarks")] F: 'static + MutateHold<A> + Mutate<A>,
385		R: 'static + Get<F::Reason>,
386		D: 'static + Convert<Fp, F::Balance>,
387		Fp: 'static,
388	> Consideration<A, Fp> for LoneHoldConsideration<A, F, R, D, Fp>
389{
390	fn new(who: &A, footprint: Fp) -> Result<Self, DispatchError> {
391		ensure!(F::balance_on_hold(&R::get(), who).is_zero(), DispatchError::Unavailable);
392		F::set_on_hold(&R::get(), who, D::convert(footprint)).map(|_| Self(PhantomData))
393	}
394	fn update(self, who: &A, footprint: Fp) -> Result<Self, DispatchError> {
395		F::set_on_hold(&R::get(), who, D::convert(footprint)).map(|_| Self(PhantomData))
396	}
397	fn drop(self, who: &A) -> Result<(), DispatchError> {
398		F::release_all(&R::get(), who, BestEffort).map(|_| ())
399	}
400	fn burn(self, who: &A) {
401		let _ = F::burn_all_held(&R::get(), who, BestEffort, Force);
402	}
403	#[cfg(feature = "runtime-benchmarks")]
404	fn ensure_successful(who: &A, fp: Fp) {
405		let _ = F::mint_into(who, F::minimum_balance().saturating_add(D::convert(fp)));
406	}
407}