frame_support/traits/tokens/fungible/freeze.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 putting freezes within a single fungible token class.
19//!
20//! See the [`crate::traits::fungible`] doc for more information about fungible traits
21//! including the place of the Freezes in FRAME.
22
23use scale_info::TypeInfo;
24use sp_arithmetic::{
25 traits::{CheckedAdd, CheckedSub},
26 ArithmeticError,
27};
28use sp_runtime::{DispatchResult, TokenError};
29
30use crate::{ensure, traits::tokens::Fortitude};
31
32/// Trait for inspecting a fungible asset which can be frozen. Freezing is essentially setting a
33/// minimum balance bellow which the total balance (inclusive of any funds placed on hold) may not
34/// be normally allowed to drop. Generally, freezers will provide an "update" function such that
35/// if the total balance does drop below the limit, then the freezer can update their housekeeping
36/// accordingly.
37pub trait Inspect<AccountId>: super::Inspect<AccountId> {
38 /// An identifier for a freeze.
39 type Id: codec::Encode + TypeInfo + 'static;
40
41 /// Amount of funds frozen in reserve by `who` for the given `id`.
42 fn balance_frozen(id: &Self::Id, who: &AccountId) -> Self::Balance;
43
44 /// The amount of the balance which can become frozen. Defaults to `total_balance()`.
45 fn balance_freezable(who: &AccountId) -> Self::Balance {
46 Self::total_balance(who)
47 }
48
49 /// Returns `true` if it's possible to introduce a freeze for the given `id` onto the
50 /// account of `who`. This will be true as long as the implementor supports as many
51 /// concurrent freezes as there are possible values of `id`.
52 fn can_freeze(id: &Self::Id, who: &AccountId) -> bool;
53}
54
55/// Trait for introducing, altering and removing freezes for an account for its funds never
56/// go below a set minimum.
57pub trait Mutate<AccountId>: Inspect<AccountId> {
58 /// Prevent actions which would reduce the balance of the account of `who` below the given
59 /// `amount` and identify this restriction though the given `id`. Unlike `extend_freeze`, any
60 /// outstanding freeze in place for `who` under the `id` are dropped.
61 ///
62 /// If `amount` is zero, it is equivalent to using `thaw`.
63 ///
64 /// Note that `amount` can be greater than the total balance, if desired.
65 fn set_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult;
66
67 /// Prevent the balance of the account of `who` from being reduced below the given `amount` and
68 /// identify this restriction though the given `id`. Unlike `set_freeze`, this does not
69 /// counteract any pre-existing freezes in place for `who` under the `id`. Also unlike
70 /// `set_freeze`, in the case that `amount` is zero, this is no-op and never fails.
71 ///
72 /// Note that more funds can be frozen than the total balance, if desired.
73 fn extend_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult;
74
75 /// Remove an existing freeze.
76 fn thaw(id: &Self::Id, who: &AccountId) -> DispatchResult;
77
78 /// Attempt to alter the amount frozen under the given `id` to `amount`.
79 ///
80 /// Fail if the account of `who` has fewer freezable funds than `amount`, unless `fortitude` is
81 /// [`Fortitude::Force`].
82 fn set_frozen(
83 id: &Self::Id,
84 who: &AccountId,
85 amount: Self::Balance,
86 fortitude: Fortitude,
87 ) -> DispatchResult {
88 let force = fortitude == Fortitude::Force;
89 ensure!(force || Self::balance_freezable(who) >= amount, TokenError::FundsUnavailable);
90 Self::set_freeze(id, who, amount)
91 }
92
93 /// Attempt to set the amount frozen under the given `id` to `amount`, iff this would increase
94 /// the amount frozen under `id`. Do nothing otherwise.
95 ///
96 /// Fail if the account of `who` has fewer freezable funds than `amount`, unless `fortitude` is
97 /// [`Fortitude::Force`].
98 fn ensure_frozen(
99 id: &Self::Id,
100 who: &AccountId,
101 amount: Self::Balance,
102 fortitude: Fortitude,
103 ) -> DispatchResult {
104 let force = fortitude == Fortitude::Force;
105 ensure!(force || Self::balance_freezable(who) >= amount, TokenError::FundsUnavailable);
106 Self::extend_freeze(id, who, amount)
107 }
108
109 /// Decrease the amount which is being frozen for a particular freeze, failing in the case of
110 /// underflow.
111 fn decrease_frozen(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult {
112 let a = Self::balance_frozen(id, who)
113 .checked_sub(&amount)
114 .ok_or(ArithmeticError::Underflow)?;
115 Self::set_freeze(id, who, a)
116 }
117
118 /// Increase the amount which is being frozen for a particular freeze, failing in the case that
119 /// too little balance is available for being frozen.
120 fn increase_frozen(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult {
121 let a = Self::balance_frozen(id, who)
122 .checked_add(&amount)
123 .ok_or(ArithmeticError::Overflow)?;
124 Self::set_frozen(id, who, a, Fortitude::Polite)
125 }
126}