referrerpolicy=no-referrer-when-downgrade

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