referrerpolicy=no-referrer-when-downgrade

frame_support/traits/tokens/fungible/
regular.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//! `Inspect` and `Mutate` traits for working with regular balances.
19//!
20//! See the [`crate::traits::fungible`] doc for more information about fungible traits.
21
22use crate::{
23	ensure,
24	traits::{
25		tokens::{
26			misc::{
27				Balance, DepositConsequence,
28				Fortitude::{self, Force, Polite},
29				Precision::{self, BestEffort, Exact},
30				Preservation::{self, Expendable},
31				Provenance::{self, Extant},
32				WithdrawConsequence,
33			},
34			Imbalance as ImbalanceT,
35		},
36		SameOrOther, TryDrop,
37	},
38};
39use core::marker::PhantomData;
40use sp_arithmetic::traits::{CheckedAdd, CheckedSub, One, Zero};
41use sp_runtime::{
42	traits::{Bounded, Saturating},
43	ArithmeticError, DispatchError, TokenError,
44};
45
46use super::{Credit, Debt, HandleImbalanceDrop, Imbalance};
47
48/// Trait for providing balance-inspection access to a fungible asset.
49pub trait Inspect<AccountId>: Sized {
50	/// Scalar type for representing balance of an account.
51	type Balance: Balance;
52
53	/// The total amount of issuance in the system.
54	fn total_issuance() -> Self::Balance;
55
56	/// The total amount of issuance in the system excluding those which are controlled by the
57	/// system.
58	fn active_issuance() -> Self::Balance {
59		Self::total_issuance()
60	}
61
62	/// The minimum balance any single account may have.
63	fn minimum_balance() -> Self::Balance;
64
65	/// Get the total amount of funds whose ultimate beneficial ownership can be determined as
66	/// `who`.
67	///
68	/// This may include funds which are wholly inaccessible to `who`, either temporarily or even
69	/// indefinitely.
70	///
71	/// For the amount of the balance which is currently free to be removed from the account without
72	/// error, use [`Inspect::reducible_balance`].
73	///
74	/// For the amount of the balance which may eventually be free to be removed from the account,
75	/// use `balance()`.
76	fn total_balance(who: &AccountId) -> Self::Balance;
77
78	/// Get the balance of `who` which does not include funds which are exclusively allocated to
79	/// subsystems of the chain ("on hold" or "reserved").
80	///
81	/// In general this isn't especially useful outside of tests, and for practical purposes, you'll
82	/// want to use [`Inspect::reducible_balance`].
83	fn balance(who: &AccountId) -> Self::Balance;
84
85	/// Get the maximum amount that `who` can withdraw/transfer successfully based on whether the
86	/// account should be kept alive (`preservation`) or whether we are willing to force the
87	/// reduction and potentially go below user-level restrictions on the minimum amount of the
88	/// account.
89	///
90	/// Always less than or equal to [`Inspect::balance`].
91	fn reducible_balance(
92		who: &AccountId,
93		preservation: Preservation,
94		force: Fortitude,
95	) -> Self::Balance;
96
97	/// Returns `true` if the balance of `who` may be increased by `amount`.
98	///
99	/// - `who`: The account of which the balance should be increased by `amount`.
100	/// - `amount`: How much should the balance be increased?
101	/// - `provenance`: Will `amount` be minted to deposit it into `account` or is it already in the
102	///   system?
103	fn can_deposit(
104		who: &AccountId,
105		amount: Self::Balance,
106		provenance: Provenance,
107	) -> DepositConsequence;
108
109	/// Returns `Success` if the balance of `who` may be decreased by `amount`, otherwise
110	/// the consequence.
111	fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance>;
112}
113
114/// Special dust type which can be type-safely converted into a [`Credit`].
115#[must_use]
116pub struct Dust<A, T: Inspect<A>>(pub T::Balance);
117
118impl<A, T: Balanced<A>> Dust<A, T> {
119	/// Convert `Dust` into an instance of `Credit`.
120	pub fn into_credit(self) -> Credit<A, T> {
121		Credit::<A, T>::new(self.0)
122	}
123}
124
125/// A fungible token class where the balance can be set arbitrarily.
126///
127/// **WARNING**
128/// Do not use this directly unless you want trouble, since it allows you to alter account balances
129/// without keeping the issuance up to date. It has no safeguards against accidentally creating
130/// token imbalances in your system leading to accidental inflation or deflation. It's really just
131/// for the underlying datatype to implement so the user gets the much safer [`Balanced`] trait to
132/// use.
133pub trait Unbalanced<AccountId>: Inspect<AccountId> {
134	/// Create some dust and handle it with [`Unbalanced::handle_dust`]. This is an unbalanced
135	/// operation and it must only be used when an account is modified in a raw fashion, outside of
136	/// the entire fungibles API. The `amount` is capped at [`Inspect::minimum_balance()`] - 1`.
137	///
138	/// This should not be reimplemented.
139	fn handle_raw_dust(amount: Self::Balance) {
140		Self::handle_dust(Dust(amount.min(Self::minimum_balance().saturating_sub(One::one()))))
141	}
142
143	/// Do something with the dust which has been destroyed from the system. [`Dust`] can be
144	/// converted into a [`Credit`] with the [`Balanced`] trait impl.
145	fn handle_dust(dust: Dust<AccountId, Self>);
146
147	/// Forcefully set the balance of `who` to `amount`.
148	///
149	/// If this call executes successfully, you can `assert_eq!(Self::balance(), amount);`.
150	///
151	/// For implementations which include one or more balances on hold, then these are *not*
152	/// included in the `amount`.
153	///
154	/// This function does its best to force the balance change through, but will not break system
155	/// invariants such as any Existential Deposits needed or overflows/underflows.
156	/// If this cannot be done for some reason (e.g. because the account cannot be created, deleted
157	/// or would overflow) then an `Err` is returned.
158	///
159	/// If `Ok` is returned then its inner, then `Some` is the amount which was discarded as dust
160	/// due to existential deposit requirements. The default implementation of
161	/// [`Unbalanced::decrease_balance`] and [`Unbalanced::increase_balance`] converts this into an
162	/// [`Imbalance`] and then passes it into [`Unbalanced::handle_dust`].
163	fn write_balance(
164		who: &AccountId,
165		amount: Self::Balance,
166	) -> Result<Option<Self::Balance>, DispatchError>;
167
168	/// Set the total issuance to `amount`.
169	fn set_total_issuance(amount: Self::Balance);
170
171	/// Reduce the balance of `who` by `amount`.
172	///
173	/// If `precision` is [`Exact`] and it cannot be reduced by that amount for
174	/// some reason, return `Err` and don't reduce it at all. If `precision` is [`BestEffort`], then
175	/// reduce the balance of `who` by the most that is possible, up to `amount`.
176	///
177	/// In either case, if `Ok` is returned then the inner is the amount by which is was reduced.
178	/// Minimum balance will be respected and thus the returned amount may be up to
179	/// [`Inspect::minimum_balance()`] - 1` greater than `amount` in the case that the reduction
180	/// caused the account to be deleted.
181	///
182	/// Any such excess must still be in the system when this returns — it must not have been
183	/// disposed of through [`Unbalanced::handle_dust`]. Callers account for the returned amount,
184	/// so an implementation that both reports the excess and traps it as dust makes them issue it
185	/// a second time.
186	fn decrease_balance(
187		who: &AccountId,
188		mut amount: Self::Balance,
189		precision: Precision,
190		preservation: Preservation,
191		force: Fortitude,
192	) -> Result<Self::Balance, DispatchError> {
193		let old_balance = Self::balance(who);
194		let reducible = Self::reducible_balance(who, preservation, force);
195		match precision {
196			BestEffort => amount = amount.min(reducible),
197			Exact => ensure!(reducible >= amount, TokenError::FundsUnavailable),
198		}
199
200		let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?;
201		if let Some(dust) = Self::write_balance(who, new_balance)? {
202			Self::handle_dust(Dust(dust));
203		}
204		Ok(old_balance.saturating_sub(new_balance))
205	}
206
207	/// Increase the balance of `who` by `amount`.
208	///
209	/// If it cannot be increased by that amount for some reason, return `Err` and don't increase
210	/// it at all. If Ok, return the imbalance.
211	/// Minimum balance will be respected and an error will be returned if
212	/// amount < [`Inspect::minimum_balance()`] when the account of `who` is zero.
213	fn increase_balance(
214		who: &AccountId,
215		amount: Self::Balance,
216		precision: Precision,
217	) -> Result<Self::Balance, DispatchError> {
218		let old_balance = Self::balance(who);
219		let reserved = Self::total_balance(who).saturating_sub(old_balance);
220		let new_balance = if let BestEffort = precision {
221			let max_balance = Self::Balance::max_value().saturating_sub(reserved);
222			old_balance.saturating_add(amount).min(max_balance)
223		} else {
224			old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?
225		};
226		new_balance.checked_add(&reserved).ok_or(ArithmeticError::Overflow)?;
227
228		if new_balance < Self::minimum_balance() {
229			// Attempt to increase from 0 to below minimum -> stays at zero.
230			if let BestEffort = precision {
231				Ok(Default::default())
232			} else {
233				Err(TokenError::BelowMinimum.into())
234			}
235		} else {
236			if new_balance == old_balance {
237				Ok(Default::default())
238			} else {
239				if let Some(dust) = Self::write_balance(who, new_balance)? {
240					Self::handle_dust(Dust(dust));
241				}
242				Ok(new_balance.saturating_sub(old_balance))
243			}
244		}
245	}
246
247	/// Reduce the active issuance by some amount.
248	fn deactivate(_: Self::Balance) {}
249
250	/// Increase the active issuance by some amount, up to the outstanding amount reduced.
251	fn reactivate(_: Self::Balance) {}
252}
253
254/// Trait for providing a basic fungible asset.
255pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId>
256where
257	AccountId: Eq,
258{
259	/// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't
260	/// possible then an `Err` is returned and nothing is changed.
261	fn mint_into(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
262		Self::total_issuance().checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
263		let actual = Self::increase_balance(who, amount, Exact)?;
264		Self::set_total_issuance(Self::total_issuance().saturating_add(actual));
265		Self::done_mint_into(who, amount);
266		Ok(actual)
267	}
268
269	/// Attempt to decrease the balance of `who`, burning the tokens.
270	/// The actual amount burned is derived from the `amount`, `preservation`, `precision` and
271	/// `force`, and might end up being more, less or equal to the `amount` specified.
272	///
273	/// If the burn isn't possible then an `Err` is returned and nothing is changed.
274	/// If successful, the amount of tokens reduced is returned.
275	fn burn_from(
276		who: &AccountId,
277		amount: Self::Balance,
278		preservation: Preservation,
279		precision: Precision,
280		force: Fortitude,
281	) -> Result<Self::Balance, DispatchError> {
282		let actual = Self::reducible_balance(who, preservation, force).min(amount);
283		ensure!(actual == amount || precision == BestEffort, TokenError::FundsUnavailable);
284		Self::total_issuance().checked_sub(&actual).ok_or(ArithmeticError::Overflow)?;
285		let actual = Self::decrease_balance(who, actual, BestEffort, preservation, force)?;
286		Self::set_total_issuance(Self::total_issuance().saturating_sub(actual));
287		Self::done_burn_from(who, actual);
288		Ok(actual)
289	}
290
291	/// Attempt to decrease the `asset` balance of `who` by `amount`.
292	///
293	/// Equivalent to [`Mutate::burn_from`], except with an expectation that within the bounds of
294	/// some universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The
295	/// implementation may be configured such that the total assets suspended may never be less than
296	/// the total assets resumed (which is the invariant for an issuing system), or the reverse
297	/// (which the invariant in a non-issuing system).
298	///
299	/// Because of this expectation, any metadata associated with the asset is expected to survive
300	/// the suspect-resume cycle.
301	fn shelve(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
302		let actual = Self::reducible_balance(who, Expendable, Polite).min(amount);
303		ensure!(actual == amount, TokenError::FundsUnavailable);
304		Self::total_issuance().checked_sub(&actual).ok_or(ArithmeticError::Overflow)?;
305		let actual = Self::decrease_balance(who, actual, BestEffort, Expendable, Polite)?;
306		Self::set_total_issuance(Self::total_issuance().saturating_sub(actual));
307		Self::done_shelve(who, actual);
308		Ok(actual)
309	}
310
311	/// Attempt to increase the `asset` balance of `who` by `amount`.
312	///
313	/// Equivalent to [`Mutate::mint_into`], except with an expectation that within the bounds of
314	/// some universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The
315	/// implementation may be configured such that the total assets suspended may never be less than
316	/// the total assets resumed (which is the invariant for an issuing system), or the reverse
317	/// (which the invariant in a non-issuing system).
318	///
319	/// Because of this expectation, any metadata associated with the asset is expected to survive
320	/// the suspect-resume cycle.
321	fn restore(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
322		Self::total_issuance().checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
323		let actual = Self::increase_balance(who, amount, Exact)?;
324		Self::set_total_issuance(Self::total_issuance().saturating_add(actual));
325		Self::done_restore(who, amount);
326		Ok(actual)
327	}
328
329	/// Transfer funds from one account into another.
330	///
331	/// Returns the amount credited to `dest`. With the default implementation this is `amount`
332	/// unless [`Unbalanced::decrease_balance`] folded a sub-minimum remainder of `source` into
333	/// its return, as `pallet-assets` does, in which case it exceeds `amount` by up to
334	/// `minimum_balance() - 1` and that remainder is credited to `dest` as well. Adapters such as
335	/// `ItemOf` and `UnionOf` forward that figure.
336	///
337	/// `source` may be debited by more than is returned, e.g. when a reaped remainder is passed to
338	/// `handle_dust` as `pallet-balances` does.
339	///
340	/// A transfer where the source and destination account are identical, or where `amount` is
341	/// zero, is treated as No-OP after checking the preconditions.
342	fn transfer(
343		source: &AccountId,
344		dest: &AccountId,
345		amount: Self::Balance,
346		preservation: Preservation,
347	) -> Result<Self::Balance, DispatchError> {
348		let _extra = Self::can_withdraw(source, amount).into_result(preservation != Expendable)?;
349		Self::can_deposit(dest, amount, Extant).into_result()?;
350		if source == dest || amount.is_zero() {
351			return Ok(amount);
352		}
353
354		let actual = Self::decrease_balance(source, amount, Exact, preservation, Polite)?;
355		Self::increase_balance(dest, actual, Exact)?;
356		Self::done_transfer(source, dest, actual);
357		Ok(actual)
358	}
359
360	/// Simple infallible function to force an account to have a particular balance, good for use
361	/// in tests and benchmarks but not recommended for production code owing to the lack of
362	/// error reporting.
363	///
364	/// Returns the new balance.
365	fn set_balance(who: &AccountId, amount: Self::Balance) -> Self::Balance {
366		let b = Self::balance(who);
367		if b > amount {
368			Self::burn_from(who, b - amount, Expendable, BestEffort, Force)
369				.map(|d| b.saturating_sub(d))
370		} else {
371			Self::mint_into(who, amount - b).map(|d| b.saturating_add(d))
372		}
373		.unwrap_or(b)
374	}
375
376	fn done_mint_into(_who: &AccountId, _amount: Self::Balance) {}
377	fn done_burn_from(_who: &AccountId, _amount: Self::Balance) {}
378	fn done_shelve(_who: &AccountId, _amount: Self::Balance) {}
379	fn done_restore(_who: &AccountId, _amount: Self::Balance) {}
380	fn done_transfer(_source: &AccountId, _dest: &AccountId, _amount: Self::Balance) {}
381}
382
383/// Simple handler for an imbalance drop which increases the total issuance of the system by the
384/// imbalance amount. Used for leftover debt.
385pub struct IncreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
386impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
387	for IncreaseIssuance<AccountId, U>
388{
389	fn handle(amount: U::Balance) {
390		U::set_total_issuance(U::total_issuance().saturating_add(amount))
391	}
392}
393
394/// Simple handler for an imbalance drop which decreases the total issuance of the system by the
395/// imbalance amount. Used for leftover credit.
396pub struct DecreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
397impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::Balance>
398	for DecreaseIssuance<AccountId, U>
399{
400	fn handle(amount: U::Balance) {
401		U::set_total_issuance(U::total_issuance().saturating_sub(amount))
402	}
403}
404
405/// A fungible token class where any creation and deletion of tokens is semi-explicit and where the
406/// total supply is maintained automatically.
407///
408/// This is auto-implemented when a token class has [`Unbalanced`] implemented.
409pub trait Balanced<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
410	/// The type for managing what happens when an instance of `Debt` is dropped without being used.
411	type OnDropDebt: HandleImbalanceDrop<Self::Balance>;
412	/// The type for managing what happens when an instance of `Credit` is dropped without being
413	/// used.
414	type OnDropCredit: HandleImbalanceDrop<Self::Balance>;
415
416	/// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will
417	/// typically be used to reduce an account by the same amount with e.g. [`Balanced::settle`].
418	///
419	/// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example
420	/// in the case of underflow.
421	fn rescind(amount: Self::Balance) -> Debt<AccountId, Self> {
422		let old = Self::total_issuance();
423		let new = old.saturating_sub(amount);
424		Self::set_total_issuance(new);
425		let delta = old - new;
426		Self::done_rescind(delta);
427		Imbalance::<Self::Balance, Self::OnDropDebt, Self::OnDropCredit>::new(delta)
428	}
429
430	/// Increase the total issuance by `amount` and return the according imbalance. The imbalance
431	/// will typically be used to increase an account by the same amount with e.g.
432	/// [`Balanced::resolve`].
433	///
434	/// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example
435	/// in the case of overflow.
436	fn issue(amount: Self::Balance) -> Credit<AccountId, Self> {
437		let old = Self::total_issuance();
438		let new = old.saturating_add(amount);
439		Self::set_total_issuance(new);
440		let delta = new - old;
441		Self::done_issue(delta);
442		Imbalance::<Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(delta)
443	}
444
445	/// Produce a pair of imbalances that cancel each other out exactly.
446	///
447	/// This is just the same as burning and issuing the same amount and has no effect on the
448	/// total issuance.
449	///
450	/// This could fail when we cannot issue and redeem the entire `amount`, for example in the
451	/// case where the amount would cause overflow or underflow in [`Balanced::issue`] or
452	/// [`Balanced::rescind`].
453	fn pair(
454		amount: Self::Balance,
455	) -> Result<(Debt<AccountId, Self>, Credit<AccountId, Self>), DispatchError> {
456		let issued = Self::issue(amount);
457		let rescinded = Self::rescind(amount);
458		// Need to check amount in case by some edge case both issued and rescinded are below
459		// `amount` by the exact same value
460		if issued.peek() != rescinded.peek() || issued.peek() != amount {
461			// Issued and rescinded will be dropped automatically
462			Err("Failed to issue and rescind equal amounts".into())
463		} else {
464			Ok((rescinded, issued))
465		}
466	}
467
468	/// Mints `value` into the account of `who`, creating it as needed.
469	///
470	/// If `precision` is `BestEffort` and `value` in full could not be minted (e.g. due to
471	/// overflow), then the maximum is minted, up to `value`. If `precision` is [`Exact`], then
472	/// exactly `value` must be minted into the account of `who` or the operation will fail with an
473	/// `Err` and nothing will change.
474	///
475	/// If the operation is successful, this will return `Ok` with a [`Debt`] of the total value
476	/// added to the account.
477	fn deposit(
478		who: &AccountId,
479		value: Self::Balance,
480		precision: Precision,
481	) -> Result<Debt<AccountId, Self>, DispatchError> {
482		let increase = Self::increase_balance(who, value, precision)?;
483		Self::done_deposit(who, increase);
484		Ok(Imbalance::<Self::Balance, Self::OnDropDebt, Self::OnDropCredit>::new(increase))
485	}
486
487	/// Removes `value` balance from `who` account if possible.
488	///
489	/// If `precision` is [`BestEffort`] and `value` in full could not be removed (e.g. due to
490	/// underflow), then the maximum is removed, up to `value`. If `precision` is [`Exact`], then
491	/// exactly `value` must be removed from the account of `who` or the operation will fail with an
492	/// `Err` and nothing will change.
493	///
494	/// If the removal is needed but not possible, then it returns `Err` and nothing is changed.
495	/// If the account needed to be deleted, then slightly more than `value` may be removed from the
496	/// account owning since up to (but not including) minimum balance may also need to be removed.
497	///
498	/// If the operation is successful, this will return `Ok` with a [`Credit`] of the total value
499	/// removed from the account.
500	fn withdraw(
501		who: &AccountId,
502		value: Self::Balance,
503		precision: Precision,
504		preservation: Preservation,
505		force: Fortitude,
506	) -> Result<Credit<AccountId, Self>, DispatchError> {
507		let decrease = Self::decrease_balance(who, value, precision, preservation, force)?;
508		Self::done_withdraw(who, decrease);
509		Ok(Imbalance::<Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(decrease))
510	}
511
512	/// The balance of `who` is increased in order to counter `credit`. If the whole of `credit`
513	/// cannot be countered, then nothing is changed and the original `credit` is returned in an
514	/// `Err`.
515	///
516	/// Please note: If `credit.peek()` is less than [`Inspect::minimum_balance()`], then `who` must
517	/// already exist for this to succeed.
518	fn resolve(
519		who: &AccountId,
520		credit: Credit<AccountId, Self>,
521	) -> Result<(), Credit<AccountId, Self>> {
522		let v = credit.peek();
523		let debt = match Self::deposit(who, v, Exact) {
524			Err(_) => return Err(credit),
525			Ok(d) => d,
526		};
527		let result = credit.offset(debt).try_drop();
528		debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed");
529		Ok(())
530	}
531
532	/// The balance of `who` is decreased in order to counter `debt`. If the whole of `debt`
533	/// cannot be countered, then nothing is changed and the original `debt` is returned in an
534	/// `Err`.
535	fn settle(
536		who: &AccountId,
537		debt: Debt<AccountId, Self>,
538		preservation: Preservation,
539	) -> Result<Credit<AccountId, Self>, Debt<AccountId, Self>> {
540		let amount = debt.peek();
541		let credit = match Self::withdraw(who, amount, Exact, preservation, Polite) {
542			Err(_) => return Err(debt),
543			Ok(c) => c,
544		};
545
546		match credit.offset(debt) {
547			SameOrOther::None => Ok(Credit::<AccountId, Self>::zero()),
548			SameOrOther::Same(dust) => Ok(dust),
549			SameOrOther::Other(rest) => {
550				debug_assert!(false, "ok withdraw return must be at least debt value; qed");
551				Err(rest)
552			},
553		}
554	}
555
556	fn done_rescind(_amount: Self::Balance) {}
557	fn done_issue(_amount: Self::Balance) {}
558	fn done_deposit(_who: &AccountId, _amount: Self::Balance) {}
559	fn done_withdraw(_who: &AccountId, _amount: Self::Balance) {}
560}
561
562/// Dummy implementation of [`Inspect`]
563#[cfg(feature = "std")]
564impl<AccountId> Inspect<AccountId> for () {
565	type Balance = u32;
566	fn total_issuance() -> Self::Balance {
567		0
568	}
569	fn minimum_balance() -> Self::Balance {
570		0
571	}
572	fn total_balance(_: &AccountId) -> Self::Balance {
573		0
574	}
575	fn balance(_: &AccountId) -> Self::Balance {
576		0
577	}
578	fn reducible_balance(_: &AccountId, _: Preservation, _: Fortitude) -> Self::Balance {
579		0
580	}
581	fn can_deposit(_: &AccountId, _: Self::Balance, _: Provenance) -> DepositConsequence {
582		DepositConsequence::Success
583	}
584	fn can_withdraw(_: &AccountId, _: Self::Balance) -> WithdrawConsequence<Self::Balance> {
585		WithdrawConsequence::Success
586	}
587}
588
589/// Dummy implementation of [`Unbalanced`]
590#[cfg(feature = "std")]
591impl<AccountId> Unbalanced<AccountId> for () {
592	fn handle_dust(_: Dust<AccountId, Self>) {}
593	fn write_balance(
594		_: &AccountId,
595		_: Self::Balance,
596	) -> Result<Option<Self::Balance>, DispatchError> {
597		Ok(None)
598	}
599	fn set_total_issuance(_: Self::Balance) {}
600}
601
602/// Dummy implementation of [`Mutate`]
603#[cfg(feature = "std")]
604impl<AccountId: Eq> Mutate<AccountId> for () {}
605
606#[cfg(test)]
607mod tests {
608	use super::*;
609	use core::cell::RefCell;
610
611	thread_local! {
612		static BALANCES: RefCell<[u64; 2]> = const { RefCell::new([0, 0]) };
613	}
614
615	const MIN_BALANCE: u64 = 10;
616
617	/// A fungible whose `decrease_balance` sweeps a sub-`minimum_balance` remainder into the
618	/// figure it returns, as `Unbalanced::decrease_balance` permits and `pallet-assets` does.
619	/// The excess stays in the system rather than going to `handle_dust`, so a caller that
620	/// credits the return value moves it rather than issuing it afresh.
621	struct SweepingFungible;
622
623	impl Inspect<usize> for SweepingFungible {
624		type Balance = u64;
625		fn total_issuance() -> u64 {
626			BALANCES.with(|b| b.borrow().iter().sum())
627		}
628		fn minimum_balance() -> u64 {
629			MIN_BALANCE
630		}
631		fn total_balance(who: &usize) -> u64 {
632			Self::balance(who)
633		}
634		fn balance(who: &usize) -> u64 {
635			BALANCES.with(|b| b.borrow()[*who])
636		}
637		fn reducible_balance(who: &usize, _: Preservation, _: Fortitude) -> u64 {
638			Self::balance(who)
639		}
640		fn can_deposit(_: &usize, _: u64, _: Provenance) -> DepositConsequence {
641			DepositConsequence::Success
642		}
643		fn can_withdraw(_: &usize, _: u64) -> WithdrawConsequence<u64> {
644			WithdrawConsequence::Success
645		}
646	}
647
648	impl Unbalanced<usize> for SweepingFungible {
649		fn handle_dust(_: Dust<usize, Self>) {
650			unreachable!("the sweep is reported through `decrease_balance`, not trapped");
651		}
652		fn write_balance(_: &usize, _: u64) -> Result<Option<u64>, DispatchError> {
653			unimplemented!("`decrease_balance` and `increase_balance` are provided");
654		}
655		fn set_total_issuance(_: u64) {}
656		fn decrease_balance(
657			who: &usize,
658			amount: u64,
659			_: Precision,
660			_: Preservation,
661			_: Fortitude,
662		) -> Result<u64, DispatchError> {
663			BALANCES.with(|b| {
664				let mut b = b.borrow_mut();
665				let remainder = b[*who].checked_sub(amount).ok_or(TokenError::FundsUnavailable)?;
666				let actual =
667					if !remainder.is_zero() && remainder < MIN_BALANCE { b[*who] } else { amount };
668				b[*who] -= actual;
669				Ok(actual)
670			})
671		}
672		fn increase_balance(who: &usize, amount: u64, _: Precision) -> Result<u64, DispatchError> {
673			BALANCES.with(|b| b.borrow_mut()[*who] += amount);
674			Ok(amount)
675		}
676	}
677
678	impl Mutate<usize> for SweepingFungible {}
679
680	/// `transfer` must credit the destination with what `decrease_balance` actually took, not
681	/// with what it was asked for. Crediting `amount` would debit the source 100 while
682	/// crediting 95, destroying the difference.
683	#[test]
684	fn transfer_credits_the_amount_actually_debited() {
685		BALANCES.with(|b| *b.borrow_mut() = [100, 0]);
686
687		assert_eq!(
688			<SweepingFungible as Mutate<usize>>::transfer(&0, &1, 95, Preservation::Expendable),
689			Ok(100),
690		);
691
692		assert_eq!(<SweepingFungible as Inspect<usize>>::balance(&0), 0);
693		assert_eq!(<SweepingFungible as Inspect<usize>>::balance(&1), 100);
694	}
695
696	/// A transfer that leaves a legal remainder is untouched by the above.
697	#[test]
698	fn transfer_without_a_sweep_moves_exactly_the_amount() {
699		BALANCES.with(|b| *b.borrow_mut() = [100, 0]);
700
701		assert_eq!(
702			<SweepingFungible as Mutate<usize>>::transfer(&0, &1, 90, Preservation::Expendable),
703			Ok(90),
704		);
705
706		assert_eq!(<SweepingFungible as Inspect<usize>>::balance(&0), 10);
707		assert_eq!(<SweepingFungible as Inspect<usize>>::balance(&1), 90);
708	}
709}