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