frame_support/traits/tokens/fungible/hold.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 holds 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 Holds in FRAME.
22
23use crate::{
24 ensure,
25 traits::tokens::{
26 DepositConsequence::Success,
27 Fortitude::{self, Force},
28 Precision::{self, BestEffort, Exact},
29 Preservation::{self, Protect},
30 Provenance::Extant,
31 Restriction::{self, Free, OnHold},
32 },
33};
34use scale_info::TypeInfo;
35use sp_arithmetic::{
36 traits::{CheckedAdd, CheckedSub, Zero},
37 ArithmeticError,
38};
39use sp_runtime::{DispatchError, DispatchResult, Saturating, TokenError};
40
41use super::*;
42
43/// Trait for inspecting a fungible asset whose accounts support partitioning and slashing.
44pub trait Inspect<AccountId>: super::Inspect<AccountId> {
45 /// An identifier for a hold. Used for disambiguating different holds so that
46 /// they can be individually replaced or removed and funds from one hold don't accidentally
47 /// become unreserved or slashed for another.
48 type Reason: codec::Encode + TypeInfo + 'static;
49
50 /// Amount of funds on hold (for all hold reasons) of `who`.
51 fn total_balance_on_hold(who: &AccountId) -> Self::Balance;
52
53 /// Get the maximum amount that the `total_balance_on_hold` of `who` can be reduced successfully
54 /// based on whether we are willing to force the reduction and potentially go below user-level
55 /// restrictions on the minimum amount of the account. Note: This cannot bring the account into
56 /// an inconsistent state with regards any required existential deposit.
57 ///
58 /// Never more than `total_balance_on_hold()`.
59 fn reducible_total_balance_on_hold(who: &AccountId, _force: Fortitude) -> Self::Balance {
60 Self::total_balance_on_hold(who)
61 }
62
63 /// Amount of funds on hold (for the given reason) of `who`.
64 fn balance_on_hold(reason: &Self::Reason, who: &AccountId) -> Self::Balance;
65
66 /// Returns `true` if it's possible to place (additional) funds under a hold of a given
67 /// `reason`. This may fail if the account has exhausted a limited number of concurrent
68 /// holds or if it cannot be made to exist (e.g. there is no provider reference).
69 ///
70 /// NOTE: This does not take into account changes which could be made to the account of `who`
71 /// (such as removing a provider reference) after this call is made. Any usage of this should
72 /// therefore ensure the account is already in the appropriate state prior to calling it.
73 fn hold_available(_reason: &Self::Reason, _who: &AccountId) -> bool {
74 true
75 }
76
77 /// Check to see if some `amount` of funds of `who` may be placed on hold with the given
78 /// `reason`. Reasons why this may not be true:
79 ///
80 /// - The implementor supports only a limited number of concurrent holds on an account which is
81 /// the possible values of `reason`;
82 /// - The total balance of the account is less than `amount`;
83 /// - Removing `amount` from the total balance would kill the account and remove the only
84 /// provider reference.
85 ///
86 /// Note: we pass `true` as the third argument to `reducible_balance` since we assume that if
87 /// needed the balance can slashed. If we are using a simple non-forcing reserve-transfer, then
88 /// we really ought to check that we are not reducing the funds below the freeze-limit (if any).
89 ///
90 /// NOTE: This does not take into account changes which could be made to the account of `who`
91 /// (such as removing a provider reference) after this call is made. Any usage of this should
92 /// therefore ensure the account is already in the appropriate state prior to calling it.
93 fn ensure_can_hold(
94 reason: &Self::Reason,
95 who: &AccountId,
96 amount: Self::Balance,
97 ) -> DispatchResult {
98 ensure!(
99 amount <= Self::reducible_balance(who, Protect, Force),
100 TokenError::FundsUnavailable
101 );
102 ensure!(Self::hold_available(reason, who), TokenError::CannotCreateHold);
103 Ok(())
104 }
105
106 /// Check to see if some `amount` of funds of `who` may be placed on hold for the given
107 /// `reason`. Reasons why this may not be true:
108 ///
109 /// - The implementor supports only a limited number of concurrent holds on an account which is
110 /// the possible values of `reason`;
111 /// - The main balance of the account is less than `amount`;
112 /// - Removing `amount` from the main balance would kill the account and remove the only
113 /// provider reference.
114 ///
115 /// NOTE: This does not take into account changes which could be made to the account of `who`
116 /// (such as removing a provider reference) after this call is made. Any usage of this should
117 /// therefore ensure the account is already in the appropriate state prior to calling it.
118 fn can_hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> bool {
119 Self::ensure_can_hold(reason, who, amount).is_ok()
120 }
121}
122
123/// A fungible, holdable token class where the balance on hold can be set arbitrarily.
124///
125/// **WARNING**
126/// Do not use this directly unless you want trouble, since it allows you to alter account balances
127/// without keeping the issuance up to date. It has no safeguards against accidentally creating
128/// token imbalances in your system leading to accidental inflation or deflation. It's really just
129/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to
130/// use.
131pub trait Unbalanced<AccountId>: Inspect<AccountId> {
132 /// Forcefully set the balance on hold of `who` to `amount`. This is independent of any other
133 /// balances on hold or the main ("free") balance.
134 ///
135 /// If this call executes successfully, you can `assert_eq!(Self::balance_on_hold(), amount);`.
136 ///
137 /// This function does its best to force the balance change through, but will not break system
138 /// invariants such as any Existential Deposits needed or overflows/underflows.
139 /// If this cannot be done for some reason (e.g. because the account doesn't exist) then an
140 /// `Err` is returned.
141 // Implementation note: This should increment the consumer refs if it moves total on hold from
142 // zero to non-zero and decrement in the opposite direction.
143 //
144 // Since this was not done in the previous logic, this will need either a migration or a
145 // state item which tracks whether the account is on the old logic or new.
146 fn set_balance_on_hold(
147 reason: &Self::Reason,
148 who: &AccountId,
149 amount: Self::Balance,
150 ) -> DispatchResult;
151
152 /// Reduce the balance on hold of `who` by `amount`.
153 ///
154 /// If `precision` is `Exact` and it cannot be reduced by that amount for
155 /// some reason, return `Err` and don't reduce it at all. If `precision` is `BestEffort`, then
156 /// reduce the balance of `who` by the most that is possible, up to `amount`.
157 ///
158 /// In either case, if `Ok` is returned then the inner is the amount by which is was reduced.
159 fn decrease_balance_on_hold(
160 reason: &Self::Reason,
161 who: &AccountId,
162 mut amount: Self::Balance,
163 precision: Precision,
164 ) -> Result<Self::Balance, DispatchError> {
165 let old_balance = Self::balance_on_hold(reason, who);
166 if let BestEffort = precision {
167 amount = amount.min(old_balance);
168 }
169 let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?;
170 Self::set_balance_on_hold(reason, who, new_balance)?;
171 Ok(amount)
172 }
173
174 /// Increase the balance on hold of `who` by `amount`.
175 ///
176 /// If it cannot be increased by that amount for some reason, return `Err` and don't increase
177 /// it at all. If Ok, return the imbalance.
178 fn increase_balance_on_hold(
179 reason: &Self::Reason,
180 who: &AccountId,
181 amount: Self::Balance,
182 precision: Precision,
183 ) -> Result<Self::Balance, DispatchError> {
184 let old_balance = Self::balance_on_hold(reason, who);
185 let new_balance = if let BestEffort = precision {
186 old_balance.saturating_add(amount)
187 } else {
188 old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?
189 };
190 let amount = new_balance.saturating_sub(old_balance);
191 if !amount.is_zero() {
192 Self::set_balance_on_hold(reason, who, new_balance)?;
193 }
194 Ok(amount)
195 }
196}
197
198/// Trait for mutating a fungible asset which can be placed on hold.
199pub trait Mutate<AccountId>:
200 Inspect<AccountId> + super::Unbalanced<AccountId> + Unbalanced<AccountId>
201{
202 /// Hold some funds in an account. If a hold for `reason` is already in place, then this
203 /// will increase it.
204 fn hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> DispatchResult {
205 // NOTE: This doesn't change the total balance of the account so there's no need to
206 // check liquidity.
207
208 Self::ensure_can_hold(reason, who, amount)?;
209 // Should be infallible now, but we proceed softly anyway.
210 Self::decrease_balance(who, amount, Exact, Protect, Force)?;
211 Self::increase_balance_on_hold(reason, who, amount, BestEffort)?;
212 Self::done_hold(reason, who, amount);
213 Ok(())
214 }
215
216 /// Release up to `amount` held funds in an account.
217 ///
218 /// The actual amount released is returned with `Ok`.
219 ///
220 /// If `precision` is [`Precision::BestEffort`], then the amount actually unreserved and
221 /// returned as the inner value of `Ok` may be smaller than the `amount` passed.
222 ///
223 /// NOTE! The inner of the `Ok` result variant returns the *actual* amount released. This is the
224 /// opposite of the `ReservableCurrency::unreserve()` result, which gives the amount not able
225 /// to be released!
226 fn release(
227 reason: &Self::Reason,
228 who: &AccountId,
229 amount: Self::Balance,
230 precision: Precision,
231 ) -> Result<Self::Balance, DispatchError> {
232 // NOTE: This doesn't change the total balance of the account so there's no need to
233 // check liquidity.
234
235 // We want to make sure we can deposit the amount in advance. If we can't then something is
236 // very wrong.
237 ensure!(Self::can_deposit(who, amount, Extant) == Success, TokenError::CannotCreate);
238 // Get the amount we can actually take from the hold. This might be less than what we want
239 // if we're only doing a best-effort.
240 let amount = Self::decrease_balance_on_hold(reason, who, amount, precision)?;
241 // Increase the main balance by what we took. We always do a best-effort here because we
242 // already checked that we can deposit before.
243 let actual = Self::increase_balance(who, amount, BestEffort)?;
244 Self::done_release(reason, who, actual);
245 Ok(actual)
246 }
247
248 /// Hold or release funds in the account of `who` to bring the balance on hold for `reason` to
249 /// exactly `amount`.
250 fn set_on_hold(
251 reason: &Self::Reason,
252 who: &AccountId,
253 amount: Self::Balance,
254 ) -> DispatchResult {
255 let current_amount = Self::balance_on_hold(reason, who);
256 if current_amount < amount {
257 Self::hold(reason, who, amount - current_amount)
258 } else if current_amount > amount {
259 Self::release(reason, who, current_amount - amount, Precision::Exact).map(|_| ())
260 } else {
261 Ok(())
262 }
263 }
264
265 /// Release all funds in the account of `who` on hold for `reason`.
266 ///
267 /// The actual amount released is returned with `Ok`.
268 ///
269 /// If `precision` is `BestEffort`, then the amount actually unreserved and returned as the
270 /// inner value of `Ok` may be smaller than the `amount` passed.
271 ///
272 /// NOTE! The inner of the `Ok` result variant returns the *actual* amount released. This is the
273 /// opposite of the `ReservableCurrency::unreserve()` result, which gives the amount not able
274 /// to be released!
275 fn release_all(
276 reason: &Self::Reason,
277 who: &AccountId,
278 precision: Precision,
279 ) -> Result<Self::Balance, DispatchError> {
280 Self::release(reason, who, Self::balance_on_hold(reason, who), precision)
281 }
282
283 /// Attempt to decrease the balance of `who` which is held for the given `reason` by `amount`.
284 ///
285 /// If `precision` is `BestEffort`, then as much as possible is reduced, up to `amount`, and the
286 /// amount of tokens reduced is returned. Otherwise, if the total amount can be reduced, then it
287 /// is and the amount returned, and if not, then nothing changes and `Err` is returned.
288 ///
289 /// If `force` is `Force`, then locks/freezes will be ignored. This should only be used when
290 /// conducting slashing or other activity which materially disadvantages the account holder
291 /// since it could provide a means of circumventing freezes.
292 fn burn_held(
293 reason: &Self::Reason,
294 who: &AccountId,
295 mut amount: Self::Balance,
296 precision: Precision,
297 force: Fortitude,
298 ) -> Result<Self::Balance, DispatchError> {
299 // We must check total-balance requirements if `!force`.
300 let liquid = Self::reducible_total_balance_on_hold(who, force);
301 if let BestEffort = precision {
302 amount = amount.min(liquid);
303 } else {
304 ensure!(amount <= liquid, TokenError::Frozen);
305 }
306 let amount = Self::decrease_balance_on_hold(reason, who, amount, precision)?;
307 Self::set_total_issuance(Self::total_issuance().saturating_sub(amount));
308 Self::done_burn_held(reason, who, amount);
309 Ok(amount)
310 }
311
312 /// Attempt to decrease the balance of `who` which is held for the given `reason` to zero.
313 ///
314 /// If `precision` is `BestEffort`, then as much as possible is reduced, up to `amount`, and the
315 /// amount of tokens reduced is returned. Otherwise, if the total amount can be reduced, then it
316 /// is and the amount returned, and if not, then nothing changes and `Err` is returned.
317 ///
318 /// If `force` is `Force`, then locks/freezes will be ignored. This should only be used when
319 /// conducting slashing or other activity which materially disadvantages the account holder
320 /// since it could provide a means of circumventing freezes.
321 fn burn_all_held(
322 reason: &Self::Reason,
323 who: &AccountId,
324 precision: Precision,
325 force: Fortitude,
326 ) -> Result<Self::Balance, DispatchError> {
327 let amount = Self::balance_on_hold(reason, who);
328 Self::burn_held(reason, who, amount, precision, force)
329 }
330
331 /// Transfer held funds into a destination account.
332 ///
333 /// If `mode` is `OnHold`, then the destination account must already exist and the assets
334 /// transferred will still be on hold in the destination account. If not, then the destination
335 /// account need not already exist, but must be creatable.
336 ///
337 /// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without
338 /// error.
339 ///
340 /// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be
341 /// left as `Polite` in most circumstances, but when you want the same power as a `slash`, it
342 /// may be `Force`.
343 ///
344 /// The actual amount transferred is returned, or `Err` in the case of error and nothing is
345 /// changed.
346 fn transfer_on_hold(
347 reason: &Self::Reason,
348 source: &AccountId,
349 dest: &AccountId,
350 mut amount: Self::Balance,
351 precision: Precision,
352 mode: Restriction,
353 force: Fortitude,
354 ) -> Result<Self::Balance, DispatchError> {
355 // We must check total-balance requirements if `force` is `Fortitude::Polite`.
356 let have = Self::balance_on_hold(reason, source);
357 let liquid = Self::reducible_total_balance_on_hold(source, force);
358 if let BestEffort = precision {
359 amount = amount.min(liquid).min(have);
360 } else {
361 ensure!(amount <= liquid, TokenError::Frozen);
362 ensure!(amount <= have, TokenError::FundsUnavailable);
363 }
364
365 // We want to make sure we can deposit the amount in advance. If we can't then something is
366 // very wrong.
367 ensure!(Self::can_deposit(dest, amount, Extant) == Success, TokenError::CannotCreate);
368 ensure!(mode == Free || Self::hold_available(reason, dest), TokenError::CannotCreateHold);
369
370 let amount = Self::decrease_balance_on_hold(reason, source, amount, precision)?;
371 let actual = if mode == OnHold {
372 Self::increase_balance_on_hold(reason, dest, amount, precision)?
373 } else {
374 Self::increase_balance(dest, amount, precision)?
375 };
376 Self::done_transfer_on_hold(reason, source, dest, actual);
377 Ok(actual)
378 }
379
380 /// Transfer some `amount` of free balance from `source` to become owned by `dest` but on hold
381 /// for `reason`.
382 ///
383 /// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without
384 /// error.
385 ///
386 /// `source` must obey the requirements of `keep_alive`.
387 ///
388 /// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be
389 /// left as `Polite` in most circumstances, but when you want the same power as a `slash`, it
390 /// may be `Force`.
391 ///
392 /// The amount placed on hold is returned or `Err` in the case of error and nothing is changed.
393 ///
394 /// WARNING: This may return an error after a partial storage mutation. It should be used only
395 /// inside a transactional storage context and an `Err` result must imply a storage rollback.
396 fn transfer_and_hold(
397 reason: &Self::Reason,
398 source: &AccountId,
399 dest: &AccountId,
400 amount: Self::Balance,
401 precision: Precision,
402 expendability: Preservation,
403 force: Fortitude,
404 ) -> Result<Self::Balance, DispatchError> {
405 ensure!(Self::hold_available(reason, dest), TokenError::CannotCreateHold);
406 ensure!(Self::can_deposit(dest, amount, Extant) == Success, TokenError::CannotCreate);
407 let actual = Self::decrease_balance(source, amount, precision, expendability, force)?;
408 Self::increase_balance_on_hold(reason, dest, actual, precision)?;
409 Self::done_transfer_and_hold(reason, source, dest, actual);
410 Ok(actual)
411 }
412
413 fn done_hold(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
414 fn done_release(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
415 fn done_burn_held(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {}
416 fn done_transfer_on_hold(
417 _reason: &Self::Reason,
418 _source: &AccountId,
419 _dest: &AccountId,
420 _amount: Self::Balance,
421 ) {
422 }
423 fn done_transfer_and_hold(
424 _reason: &Self::Reason,
425 _source: &AccountId,
426 _dest: &AccountId,
427 _transferred: Self::Balance,
428 ) {
429 }
430}
431
432/// Trait for slashing a fungible asset which can be place on hold.
433pub trait Balanced<AccountId>:
434 super::Balanced<AccountId>
435 + Unbalanced<AccountId>
436 + DoneSlash<Self::Reason, AccountId, Self::Balance>
437{
438 /// Reduce the balance of some funds on hold in an account.
439 ///
440 /// The resulting imbalance is the first item of the tuple returned.
441 ///
442 /// As much funds that are on hold up to `amount` will be deducted as possible. If this is less
443 /// than `amount`, then a non-zero second item will be returned.
444 fn slash(
445 reason: &Self::Reason,
446 who: &AccountId,
447 amount: Self::Balance,
448 ) -> (Credit<AccountId, Self>, Self::Balance) {
449 let decrease = Self::decrease_balance_on_hold(reason, who, amount, BestEffort)
450 .unwrap_or(Default::default());
451 let credit =
452 Imbalance::<Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(decrease);
453 Self::done_slash(reason, who, decrease);
454 (credit, amount.saturating_sub(decrease))
455 }
456}
457
458/// Trait for optional bookkeeping callbacks after a slash.
459pub trait DoneSlash<Reason, AccountId, Balance> {
460 fn done_slash(_reason: &Reason, _who: &AccountId, _amount: Balance) {}
461}
462
463#[impl_trait_for_tuples::impl_for_tuples(30)]
464impl<Reason, AccountId, Balance: Copy> DoneSlash<Reason, AccountId, Balance> for Tuple {
465 fn done_slash(reason: &Reason, who: &AccountId, amount: Balance) {
466 for_tuples!( #( Tuple::done_slash(reason, who, amount); )* );
467 }
468}