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