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>: super::Balanced<AccountId> + Unbalanced<AccountId> {
218	/// Reduce the balance of some funds on hold in an account.
219	///
220	/// The resulting imbalance is the first item of the tuple returned.
221	///
222	/// As much funds that are on hold up to `amount` will be deducted as possible. If this is less
223	/// than `amount`, then a non-zero second item will be returned.
224	fn slash(
225		asset: Self::AssetId,
226		reason: &Self::Reason,
227		who: &AccountId,
228		amount: Self::Balance,
229	) -> (Credit<AccountId, Self>, Self::Balance) {
230		let decrease =
231			Self::decrease_balance_on_hold(asset.clone(), reason, who, amount, BestEffort)
232				.unwrap_or(Default::default());
233		let credit =
234			Imbalance::<Self::AssetId, Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(
235				asset.clone(),
236				decrease,
237			);
238		Self::done_slash(asset, reason, who, decrease);
239		(credit, amount.saturating_sub(decrease))
240	}
241
242	fn done_slash(
243		_asset: Self::AssetId,
244		_reason: &Self::Reason,
245		_who: &AccountId,
246		_amount: Self::Balance,
247	) {
248	}
249}
250
251/// Trait for mutating a fungible asset which can be placed on hold.
252pub trait Mutate<AccountId>:
253	Inspect<AccountId> + super::Unbalanced<AccountId> + Unbalanced<AccountId>
254{
255	/// Hold some funds in an account. If a hold for `reason` is already in place, then this
256	/// will increase it.
257	fn hold(
258		asset: Self::AssetId,
259		reason: &Self::Reason,
260		who: &AccountId,
261		amount: Self::Balance,
262	) -> DispatchResult {
263		// NOTE: This doesn't change the total balance of the account so there's no need to
264		// check liquidity.
265
266		Self::ensure_can_hold(asset.clone(), reason, who, amount)?;
267		// Should be infallible now, but we proceed softly anyway.
268		Self::decrease_balance(asset.clone(), who, amount, Exact, Protect, Force)?;
269		Self::increase_balance_on_hold(asset.clone(), reason, who, amount, BestEffort)?;
270		Self::done_hold(asset, reason, who, amount);
271		Ok(())
272	}
273
274	/// Release up to `amount` held funds in an account.
275	///
276	/// The actual amount released is returned with `Ok`.
277	///
278	/// If `precision` is `BestEffort`, then the amount actually unreserved and returned as the
279	/// inner value of `Ok` may be smaller than the `amount` passed.
280	fn release(
281		asset: Self::AssetId,
282		reason: &Self::Reason,
283		who: &AccountId,
284		amount: Self::Balance,
285		precision: Precision,
286	) -> Result<Self::Balance, DispatchError> {
287		// NOTE: This doesn't change the total balance of the account so there's no need to
288		// check liquidity.
289
290		// We want to make sure we can deposit the amount in advance. If we can't then something is
291		// very wrong.
292		ensure!(
293			Self::can_deposit(asset.clone(), who, amount, Extant) == Success,
294			TokenError::CannotCreate
295		);
296		// Get the amount we can actually take from the hold. This might be less than what we want
297		// if we're only doing a best-effort.
298		let amount = Self::decrease_balance_on_hold(asset.clone(), reason, who, amount, precision)?;
299		// Increase the main balance by what we took. We always do a best-effort here because we
300		// already checked that we can deposit before.
301		let actual = Self::increase_balance(asset.clone(), who, amount, BestEffort)?;
302		Self::done_release(asset, reason, who, actual);
303		Ok(actual)
304	}
305
306	/// Attempt to decrease the `asset` balance of `who` which is held for the given `reason` by
307	/// `amount`.
308	///
309	/// If `precision` is true, then as much as possible is reduced, up to `amount`, and the
310	/// amount of tokens reduced is returned. Otherwise, if the total amount can be reduced, then it
311	/// is and the amount returned, and if not, then nothing changes and `Err` is returned.
312	///
313	/// If `force` is `Force`, then locks/freezes will be ignored. This should only be used when
314	/// conducting slashing or other activity which materially disadvantages the account holder
315	/// since it could provide a means of circumventing freezes.
316	fn burn_held(
317		asset: Self::AssetId,
318		reason: &Self::Reason,
319		who: &AccountId,
320		mut amount: Self::Balance,
321		precision: Precision,
322		force: Fortitude,
323	) -> Result<Self::Balance, DispatchError> {
324		// We must check total-balance requirements if `!force`.
325		let liquid = Self::reducible_total_balance_on_hold(asset.clone(), who, force);
326		if let BestEffort = precision {
327			amount = amount.min(liquid);
328		} else {
329			ensure!(amount <= liquid, TokenError::Frozen);
330		}
331		let amount = Self::decrease_balance_on_hold(asset.clone(), reason, who, amount, precision)?;
332		Self::set_total_issuance(
333			asset.clone(),
334			Self::total_issuance(asset.clone()).saturating_sub(amount),
335		);
336		Self::done_burn_held(asset, reason, who, amount);
337		Ok(amount)
338	}
339
340	/// Attempt to decrease the `asset` balance of `who` which is held for the given `reason` to
341	/// zero.
342	///
343	/// If `precision` is `BestEffort`, then as much as possible is reduced, up to `amount`, and the
344	/// amount of tokens reduced is returned. Otherwise, if the total amount can be reduced, then it
345	/// is and the amount returned, and if not, then nothing changes and `Err` is returned.
346	///
347	/// If `force` is `Force`, then locks/freezes will be ignored. This should only be used when
348	/// conducting slashing or other activity which materially disadvantages the account holder
349	/// since it could provide a means of circumventing freezes.
350	fn burn_all_held(
351		asset: Self::AssetId,
352		reason: &Self::Reason,
353		who: &AccountId,
354		precision: Precision,
355		force: Fortitude,
356	) -> Result<Self::Balance, DispatchError> {
357		let amount = Self::balance_on_hold(asset.clone(), reason, who);
358		Self::burn_held(asset, reason, who, amount, precision, force)
359	}
360
361	/// Transfer held funds into a destination account.
362	///
363	/// If `mode` is `OnHold`, then the destination account must already exist and the assets
364	/// transferred will still be on hold in the destination account. If not, then the destination
365	/// account need not already exist, but must be creatable.
366	///
367	/// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without
368	/// error.
369	///
370	/// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be
371	/// left as `Regular` in most circumstances, but when you want the same power as a `slash`, it
372	/// may be `Force`.
373	///
374	/// The actual amount transferred is returned, or `Err` in the case of error and nothing is
375	/// changed.
376	fn transfer_on_hold(
377		asset: Self::AssetId,
378		reason: &Self::Reason,
379		source: &AccountId,
380		dest: &AccountId,
381		mut amount: Self::Balance,
382		precision: Precision,
383		mode: Restriction,
384		force: Fortitude,
385	) -> Result<Self::Balance, DispatchError> {
386		// We must check total-balance requirements if `!force`.
387		let have = Self::balance_on_hold(asset.clone(), reason, source);
388		let liquid = Self::reducible_total_balance_on_hold(asset.clone(), source, force);
389		if let BestEffort = precision {
390			amount = amount.min(liquid).min(have);
391		} else {
392			ensure!(amount <= liquid, TokenError::Frozen);
393			ensure!(amount <= have, TokenError::FundsUnavailable);
394		}
395
396		// We want to make sure we can deposit the amount in advance. If we can't then something is
397		// very wrong.
398		ensure!(
399			Self::can_deposit(asset.clone(), dest, amount, Extant) == Success,
400			TokenError::CannotCreate
401		);
402		ensure!(
403			mode == Free || Self::hold_available(asset.clone(), reason, dest),
404			TokenError::CannotCreateHold
405		);
406
407		let amount =
408			Self::decrease_balance_on_hold(asset.clone(), reason, source, amount, precision)?;
409		let actual = if mode == OnHold {
410			Self::increase_balance_on_hold(asset.clone(), reason, dest, amount, precision)?
411		} else {
412			Self::increase_balance(asset.clone(), dest, amount, precision)?
413		};
414		Self::done_transfer_on_hold(asset, reason, source, dest, actual);
415		Ok(actual)
416	}
417
418	/// Transfer some `amount` of free balance from `source` to become owned by `dest` but on hold
419	/// for `reason`.
420	/// for `reason`.
421	///
422	/// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without
423	/// error.
424	///
425	/// `source` must obey the requirements of `keep_alive`.
426	///
427	/// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be
428	/// left as `Regular` in most circumstances, but when you want the same power as a `slash`, it
429	/// may be `Force`.
430	///
431	/// The amount placed on hold is returned or `Err` in the case of error and nothing is changed.
432	///
433	/// WARNING: This may return an error after a partial storage mutation. It should be used only
434	/// inside a transactional storage context and an `Err` result must imply a storage rollback.
435	fn transfer_and_hold(
436		asset: Self::AssetId,
437		reason: &Self::Reason,
438		source: &AccountId,
439		dest: &AccountId,
440		amount: Self::Balance,
441		precision: Precision,
442		expendability: Preservation,
443		force: Fortitude,
444	) -> Result<Self::Balance, DispatchError> {
445		ensure!(Self::hold_available(asset.clone(), reason, dest), TokenError::CannotCreateHold);
446		ensure!(
447			Self::can_deposit(asset.clone(), dest, amount, Extant) == Success,
448			TokenError::CannotCreate
449		);
450		let actual =
451			Self::decrease_balance(asset.clone(), source, amount, precision, expendability, force)?;
452		Self::increase_balance_on_hold(asset.clone(), reason, dest, actual, precision)?;
453		Self::done_transfer_on_hold(asset, reason, source, dest, actual);
454		Ok(actual)
455	}
456
457	fn done_hold(
458		_asset: Self::AssetId,
459		_reason: &Self::Reason,
460		_who: &AccountId,
461		_amount: Self::Balance,
462	) {
463	}
464	fn done_release(
465		_asset: Self::AssetId,
466		_reason: &Self::Reason,
467		_who: &AccountId,
468		_amount: Self::Balance,
469	) {
470	}
471	fn done_burn_held(
472		_asset: Self::AssetId,
473		_reason: &Self::Reason,
474		_who: &AccountId,
475		_amount: Self::Balance,
476	) {
477	}
478	fn done_transfer_on_hold(
479		_asset: Self::AssetId,
480		_reason: &Self::Reason,
481		_source: &AccountId,
482		_dest: &AccountId,
483		_amount: Self::Balance,
484	) {
485	}
486	fn done_transfer_and_hold(
487		_asset: Self::AssetId,
488		_reason: &Self::Reason,
489		_source: &AccountId,
490		_dest: &AccountId,
491		_transferred: Self::Balance,
492	) {
493	}
494}