referrerpolicy=no-referrer-when-downgrade

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}